Tag: Search

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

Table of Contents

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

What do you get for using a search input type?

Like this: <input type="search">

  1. You get an extra-round-y appearance in Safari, which at one time matched the macOS look for search inputs, but not really anymore. I don’t hate the look, except…
  2. Safari totally ignores the font-size you set on it, so careful about that. Unless you smash off the round-y look with -webkit-appearance: none—then you can, so probably 92% of all websites do this.
  3. You get a little × icon inside the input (when it has a value) that clears it out. This is probably the best reason to use it, and mercifiully you get to keep it even after resetting the appearance.
  4. You get the satisfaction of knowing that you’re using the semantically correct input type for the job, assuming you are building a thing that actually searches something.
  5. You get past search terms with autocomplete. Browsers store those past search terms and offer a menu for autocompleting them. I’m trying to make this a list of things that are unique to search inputs, and I can prove this happens on non-search inputs. But hey, it does make the most sense on search inputs.
  6. You get the pleasure of adding a role to the parent form (i.e. <form role="search">) because that helps assistive technology announce it as a search form.
  7. You get to remember to add a proper <label> to the input. Just using a magnifying glass icon or just a placeholder alone won’t cut it, even though that’s what a lot of designs call for visually.
  8. You get a super weird incremental attribute that sends a debounced search event to the DOM element itself. I guess that is useful for live search UX. But it’s non-standard and not in Firefox so probably don’t count on it (via Christian Shaefer).
  9. You get to live another day as a front-end developer.

The post What do you get for using a search input type? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

In-Page Filtered Search With Vanilla JavaScript

If you have a page that includes a lot of information, it’s a good idea to let users search for what they might be looking for. I’m not talking about searching a database or even searching JSON data — I’m talking about literally searching text on a single rendered web page. Users can already use the built-in browser search for this, but we can augment that by offering our own search functionality that filters down the page making matching results easier to find and read.

Here’s a live demo of what we’re going to build:

I use this same technique on my real project: https://freestuff.dev/.

Meet JavaScript!

Well, you might know JavaScript already. JavaScript is going to handle all the interactivity in this journey. It’s going to…

  • find all the content we want to search through,
  • watch what a user types in the search input,
  • filter the innerText of the searchable elements,
  • test if the text includes the search term (.includes() is the heavy lifter here!), and
  • toggle the visibility of the (parent) elements, depending on if they include the search term or not.

Alright, we have our requirements! Let’s start working.

The basic markup

Let’s assume we have a FAQ page. Each question is a “card” which has a title and content:

<h1>FAQ Section</h1>  <div class="cards">   <h3>Who are we</h3>   <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p> </div>  <div class="cards">   <h3>What we do</h3>   <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p> </div>  <div class="cards">   <h3>Why work here</h3>   <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p> </div>  <div class="cards">   <h3>Learn more</h3>   <p>Want to learn more about us?</p> </div>

Imagine there are a lot of questions on this page.

To get ready for the interactivity, we’ll have this one line of CSS. This gives us a class we can add/remove depending on the search situation when we get to the JavaScript:

.is-hidden { display: none; }

Let’s add a search input with an event that fires when it is interacted with:

<label for="searchbox">Search</label> <input    type="search"    oninput="liveSearch()"    id="searchbox"  >

The JavaScript baseline

And here’s the JavaScript that does everything else!

function liveSearch() {   // Locate the card elements   let cards = document.querySelectorAll('.cards')   // Locate the search input   let search_query = document.getElementById("searchbox").value;   // Loop through the cards   for (var i = 0; i < cards.length; i++) {     // If the text is within the card...     if(cards[i].innerText.toLowerCase()       // ...and the text matches the search query...       .includes(search_query.toLowerCase())) {         // ...remove the `.is-hidden` class.         cards[i].classList.remove("is-hidden");     } else {       // Otherwise, add the class.       cards[i].classList.add("is-hidden");     }   } }

You can probably go line-by-line there and reason out what it is doing. It finds all the cards and the input and saves references to them. When a search event fires, it loops through all the cards, determines if the text is within the card or not. It the text in the card matches the search query, the .is-hidden class is removed to show the card; if not, the class is there and the card remains hidden.

Here is the link to the demo again.

Adding a delay

To make sure our JavaScript doesn’t run too much (meaning it would slow down the page), we will run our liveSearch function only after waiting an “X” number of seconds.

<!-- Delete on Input event on this input --> <label for="searchbox">Search</label> <input type="search" id="searchbox">
// A little delay let typingTimer;         let typeInterval = 500; // Half a second let searchInput = document.getElementById('searchbox');  searchInput.addEventListener('keyup', () => {   clearTimeout(typingTimer);   typingTimer = setTimeout(liveSearch, typeInterval); });

What about fuzzy searches?

Let’s say you want to search by text that is not visible to user. The idea is sort of like a fuzzy search, where related keywords return the same result as an exact match. This helps expand the number of cards that might “match” a search query.

There are two ways to do this. The first is using a hidden element, like a span, that contains keywords:

<div class="cards">   <h3>Who are we</h3>   <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p>        <!-- Put any keywords here -->    <span class="is-hidden">secret</span>  </div>

Then we need to update our liveSearch function. Instead of using .innerText we will use .textContent to includes hidden elements. See more detail about the difference between innerText and textContent here

 for (var i = 0; i < cards.length; i++) {         if(cards[i].textContent.toLowerCase()                 .includes(search_query.toLowerCase())) {             cards[i].classList.remove("is-hidden");         } else {             cards[i].classList.add("is-hidden");         }     }

Try typing “secret” on a search box, it should reveal this card, even though “secret” isn’t a displayed on the page.

A second approach is searching through an attribute. Let’s say we have a gallery of images. We can put the keywords directly on the alt attribute of the image. Try typing “kitten” or “human” in the next demo. Those queries are matching what’s contained in the image alt text.

For this to work, we need to change innerText to getAttribute('alt') since we want to look through alt attributes in addition to what’s actually visible on the page.

for (var i = 0; i < cards.length; i++) {   if(cards[i].getAttribute('alt').toLowerCase()     .includes(search_query.toLowerCase())) {       cards[i].classList.remove("is-hidden");   } else {     cards[i].classList.add("is-hidden");   } }

Depending on your needs, you could put your keywords in another attribute, or perhaps a custom one.

Caveat

Again, this isn’t a search technology that works by querying a database or other data source. It works only if you have all the searchable content in the DOM on that page, already rendered.

So, yeah, there’s that. Just something to keep in mind.

Wrapping up

Obviously, I really like this technique, enough to use it on a production site. But how else might you use something like this? An FAQ page is a clear candidate, as we saw, but any situation that calls for filtering any sort of content is fit for this sort of thing. Even a gallery of images could work, using the hidden input trick to search through the alt tag content of the images.

Whatever the case, I hope you find this helpful. I was surprised that we can get a decently robust search solution with a few lines of vanilla JavaScript.

Have you used this technique before, or something like it? What was your use case?


The post In-Page Filtered Search With Vanilla JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

What is Your Page Title on a Google Search Engine Results Page?

Whatever Google wants it to be. I always thought it was exactly what your <title> element was. Perhaps in lieu of that, what the first <h1> on the page is. But recently I noticed some pages on this site that were showing a title on SERPs that was a string that appeared nowhere at all in the source of the page.

When I first noticed it, I tweeted my basic findings…

The thread has more info than what unfurled here.

This is a known thing. Apparently, they’ve been doing this for a long time (~10 years), but it’s the first I’ve noticed it. And it’s undergone a recent change:

The article is pretty clear about things:

[…] we think our new system is producing titles that work better for documents overall, to describe what they are about, regardless of the particular query.

Also, while we’ve gone beyond HTML text to create titles for over a decade, our new system is making even more use of such text. In particular, we are making use of text that humans can visually see when they arrive at a web page. We consider the main visual title or headline shown on a page, content that site owners often place within <H1> tags or other header tags, and content that’s large and prominent through the use of style treatments.

Other text contained in the page might be considered, as might be text within links that point at pages.

The change is in response to people having sucky <title> text. Like it’s too long, too jacked up with SEO garbage (irony!), or are just plain non-descriptive.

I’m not entirely sure how much I care just yet.

Part of me thinks, well, google.com isn’t the web. As important as it is, it’s a proprietary product by a private company and they can do whatever they want within the bounds of the law. In this case, it’s clear the intention is to help: to provide titles that are more clear than what the original page has.

Part of me thinks, well, that sucks that, as site owners, we have no control. If Google wanted to change the SERP title for every results to this website to “CSS-Tricks is a stupid website, never visit it,” they could and that’s that.

Part of me connects this kind of work to AMP. AMP was basically saying, “Y’all are absolutely horrible at building performant mobile websites, so we’re going to build a strict set of rules such that you can’t screw it up anymore, and dangling a carrot of better SERP placement if you buy into the rules.” This way of creating page titles is basically saying, “Y’all are absolutely horrible at providing good titles, so we’re going to title your pages for you so you can’t screw it up anymore and we can improve our SERPs.”

Except with AMP, you had to put in the development hours to make it happen. It was opt-in, even if the carrot was un-ignorable by content companies. This doesn’t carry the risk of burning development hours, but it’s also not something we get to opt into.


The post What is Your Page Title on a Google Search Engine Results Page? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Native Search vs. Jetpack Instant Search in Headless WordPress With Gatsby

Have you already tried using WordPress headlessly with Gatsby? If you haven’t, you might check this article around the new Gatsby source plugin for WordPress; gatsby-source-wordpress is the official source plugin introduced in March 2021 as a part of the Gatsby 3 release. It significantly improves the integration with WordPress. Also, the WordPress plugin WPGraphQL providing the GraphQL API is now available via the official WordPress repository.

With stable and maintained tools, developing Gatsby websites powered by WordPress becomes easier and more interesting. I got myself involved in this field, I co-founded (with Alexandra Spalato), and recently launched Gatsby WP Themes — a niche marketplace for developers building WordPress-powered sites with Gatsby. In this article, I would love to share my insights and, in particular, discuss the search functionality.

Search does not come out of the box, but there are many options to consider. I will focus on two distinct possibilities — taking advantage of WordPress native search (WordPress search query) vs. using Jetpack Instant Search.

Getting started

Let’s start by setting up a WordPress-powered Gatsby website. For the sake of simplicity, I will follow the getting started instructions and install the gatsby-starter-wordpress-blog starter.

gatsby new gatsby-wordpress-w-search https://github.com/gatsbyjs/gatsby-starter-wordpress-blog 

This simple, bare-bone starter creates routes exclusively for individual posts and blog pages. But we can keep it that simple here. Let’s imagine that we don’t want to include pages within the search results.

For the moment, I will leave the WordPress source website as it is and pull the content from the starter author’s WordPress demo. If you use your own source, just remember that there are two plugins required on the WordPress end (both available via the plugin repository):

  • WPGraphQL – a plugin that runs a GraphQL server on the WordPress instance
  • WPGatsby – a plugin that modifies the WPGraphQL schema in Gatsby-specific ways (it also adds some mechanism to optimize the build process)

Setting up Apollo Client

With Gatsby, we usually either use the data from queries run on page creation (page queries) or call the useStaticQuery hook. The latter is available in components and does not allow dynamic query parameters; its role is to retrieve GraphQL data at build time. None of those two query solutions works for a user’s-initiated search. Instead, we will ask WordPress to run a search query and send us back the results. Can we send a graphQL search query? Yes! WPGraphQL provides search; you can search posts in WPGraphQL like so:

posts(where: {search: "gallery"}) {   nodes {     id     title     content   } } 

In order to communicate directly with our WPGraphQL API, we will install Apollo Client; it takes care of requesting and caching the data as well as updating our UI components.

yarn add @apollo/client cross-fetch 

To access Apollo Client anywhere in our component tree, we need to wrap our app with ApolloProvider. Gatsby does not expose the App component that wraps around the whole application. Instead, it provides the wrapRootElement API. It’s a part of the Gatsby Browser API and needs to be implemented in the gatsby-browser.js file located at the project’s root.

// gatsby-browser.js import React from "react" import fetch from "cross-fetch" import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" const cache = new InMemoryCache() const link = new HttpLink({   /* Set the endpoint for your GraphQL server, (same as in gatsby-config.js) */   uri: "https://wpgatsbydemo.wpengine.com/graphql",   /* Use fetch from cross-fetch to provide replacement for server environment */   fetch }) const client = new ApolloClient({   link,   cache, }) export const wrapRootElement = ({ element }) => (   <ApolloProvider client={client}>{element}</ApolloProvider> ) 

SearchForm component

Now that we’ve set up ApolloClient, let’s build our Search component.

touch src/components/search.js src/components/search-form.js src/components/search-results.js src/css/search.css 

The Search component wraps SearchForm and SearchResults

// src/components/search.js import React, { useState } from "react" import SearchForm from "./search-form" import SearchResults from "./search-results"  const Search = () => {   const [searchTerm, setSearchTerm] = useState("")   return (     <div className="search-container">       <SearchForm setSearchTerm={setSearchTerm} />       {searchTerm && <SearchResults searchTerm={searchTerm} />}     </div>   ) } export default Search

<SearchForm /> is a simple form with controlled input and a submit handler that sets the searchTerm state value to the user submission.

// src/components/search-form.js import React, { useState } from "react" const SearchForm = ({ searchTerm, setSearchTerm }) => {   const [value, setValue] = useState(searchTerm)   const handleSubmit = e => {     e.preventDefault()     setSearchTerm(value)   }   return (     <form role="search" onSubmit={handleSubmit}>       <label htmlFor="search">Search blog posts:</label>       <input         id="search"         type="search"         value={value}         onChange={e => setValue(e.target.value)}       />       <button type="submit">Submit</button>     </form>   ) } export default SearchForm

The SearchResults component receives the searchTerm via props, and that’s where we use Apollo Client.

For each searchTerm, we would like to display the matching posts as a list containing the post’s title, excerpt, and a link to this individual post. Our query will be like so:

const GET_RESULTS = gql`   query($  searchTerm: String) {     posts(where: { search: $  searchTerm }) {       edges {         node {           id           uri           title           excerpt         }       }     }   } `

We will use the useQuery hook from @apollo-client to run the GET_RESULTS query with a search variable.

// src/components/search-results.js import React from "react" import { Link } from "gatsby" import { useQuery, gql } from "@apollo/client" const GET_RESULTS = gql`   query($  searchTerm: String) {     posts(where: { search: $  searchTerm }) {       edges {         node {           id           uri           title           excerpt         }       }     }   } ` const SearchResults = ({ searchTerm }) => {   const { data, loading, error } = useQuery(GET_RESULTS, {     variables: { searchTerm }   })   if (loading) return <p>Searching posts for {searchTerm}...</p>   if (error) return <p>Error - {error.message}</p>   return (     <section className="search-results">       <h2>Found {data.posts.edges.length} results for {searchTerm}:</h2>       <ul>         {data.posts.edges.map(el => {           return (             <li key={el.node.id}>               <Link to={el.node.uri}>{el.node.title}</Link>             </li>           )         })}       </ul>     </section>   ) } export default SearchResults 

The useQuery hook returns an object that contains loading, error, and data properties. We can render different UI elements according to the query’s state. As long as loading is truthy, we display <p>Searching posts...</p>. If loading and error are both falsy, the query has completed and we can loop over the data.posts.edges and display the results.

if (loading) return <p>Searching posts...</p> if (error) return <p>Error - {error.message}</p> // else return ( //... )

For the moment, I am adding the <Search /> to the layout component. (I’ll move it somewhere else a little bit later.) Then, with some styling and a visible state variable, I made it feel more like a widget, opening on click and fixed-positioned in the top right corner.

Paginated queries

Without the number of entries specified, the WPGraphQL posts query returns ten first posts; we need to take care of the pagination. WPGraphQL implements the pagination following the Relay Specification for GraphQL Schema Design. I will not go into the details; let’s just note that it is a standardized pattern. Within the Relay specification, in addition to posts.edges (which is a list of { cursor, node } objects), we have access to the posts.pageInfo object that provides:

  • endCursor – cursor of the last item in posts.edges,
  • startCursor – cursor of the first item in posts.edges,
  • hasPreviousPage – boolean for “are there more results available (backward),” and
  • hasNextPage – boolean for “are there more results available (forward).”

We can modify the slice of the data we want to access with the additional query variables:

  • first – the number of returned entries
  • after – the cursor we should start after

How do we deal with pagination queries with Apollo Client? The recommended approach is to use the fetchMore function, that is (together with loading, error and data) a part of the object returned by the useQuery hook.

// src/components/search-results.js import React from "react" import { Link } from "gatsby" import { useQuery, gql } from "@apollo/client" const GET_RESULTS = gql`   query($  searchTerm: String, $  after: String) {     posts(first: 10, after: $  after, where: { search: $  searchTerm }) {       edges {         node {           id           uri           title         }       }       pageInfo {         hasNextPage         endCursor       }     }   } ` const SearchResults = ({ searchTerm }) => {   const { data, loading, error, fetchMore } = useQuery(GET_RESULTS, {     variables: { searchTerm, after: "" },   })   if (loading && !data) return <p>Searching posts for {searchTerm}...</p>   if (error) return <p>Error - {error.message}</p>   const loadMore = () => {     fetchMore({       variables: {         after: data.posts.pageInfo.endCursor,       },       // with notifyOnNetworkStatusChange our component re-renders while a refetch is in flight so that we can mark loading state when waiting for more results (see lines 42, 43)       notifyOnNetworkStatusChange: true,     })   }    return (     <section className="search-results">       {/* as before */}       {data.posts.pageInfo.hasNextPage && (         <button type="button" onClick={loadMore} disabled={loading}>           {loading ? "Loading..." : "More results"}         </button>       )}     </section>   ) } export default SearchResults 

The first argument has its default value but is necessary here to indicate that we are sending a paginated request. Without first, pageInfo.hasNextPage will always be false, no matter the search keyword.

Calling fetchMore fetches the next slice of results but we still need to tell Apollo how it should merge the “fetch more” results with the existing cached data. We specify all the pagination logic in a central location as an option passed to the InMemoryCache constructor (in the gatsby-browser.js file). And guess what? With the Relay specification, we’ve got it covered — Apollo Client provides the relayStylePagination function that does all the magic for us.

// gatsby-browser.js import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" import { relayStylePagination } from "@apollo/client/utilities" const cache = new InMemoryCache({   typePolicies: {     Query: {       fields: {         posts: relayStylePagination(["where"]),       },     },   }, }) /* as before */ 

Just one important detail: we don’t paginate all posts, but instead the posts that correspond to a specific where condition. Adding ["where"] as an argument to relayStylePagination creates a distinct storage key for different search terms.

Making search persistent

Right now my Search component lives in the Layout component. It’s displayed on every page but gets unmounted every time the route changes. What if we could keep the search results while navigating? We can take advantage of the Gatsby wrapPageElement browser API to set persistent UI elements around pages.

Let’s move <Search /> from the layout component to the wrapPageElement:

// gatsby-browser.js import Search from "./src/components/search" /* as before */ export const wrapPageElement = ({ element }) => {   return <><Search />{element}</> } 

The APIs wrapPageElement and wrapRootElement exist in both the browser and Server-Side Rendering (SSR) APIs. Gatsby recommends that we implement wrapPageElement and wrapRootElement in both gatsby-browser.js and gatsby-ssr.js. Let’s create the gatsby-ssr.js (in the root of the project) and re-export our elements:

// gatsby-ssr.js export { wrapRootElement, wrapPageElement } from "./gatsby-browser" 

I deployed a demo where you can see it in action. You can also find the code in this repo.

The wrapPageElement approach may not be ideal in all cases. Our search widget is “detached” from the layout component. It works well with the position “fixed” like in our working example or within an off-canvas sidebar like in this Gatsby WordPress theme.

But what if you want to have “persistent” search results displayed within a “classic” sidebar? In that case, you could move the searchTerm state from the Search component to a search context provider placed within the wrapRootElement:

// gatsby-browser.js import SearchContextProvider from "./src/search-context" /* as before */ export const wrapRootElement = ({ element }) => (   <ApolloProvider client={client}>     <SearchContextProvider>       {element}     </SearchContextProvider>   </ApolloProvider> ) 

…with the SearchContextProvider defined as below:

// src/search-context.js import React, {createContext, useState} from "react" export const SearchContext = createContext() export const SearchContextProvider = ({ children }) => {   const [searchTerm, setSearchTerm] = useState("")   return (     <SearchContext.Provider value={{ searchTerm, setSearchTerm }}>       {children}     </SearchContext.Provider>   ) }

You can see it in action in another Gatsby WordPress theme:

Note how, since Apollo Client caches the search results, we immediately get them on the route change.

Results from posts and pages

If you checked the theme examples above, you might have noticed how I deal with querying more than just posts. My approach is to replicate the same logic for pages and display results for each post type separately.

Alternatively, you could use the Content Node interface to query nodes of different post types in a single connection:

const GET_RESULTS = gql`   query($  searchTerm: String, $  after: String) {     contentNodes(first: 10, after: $  after, where: { search: $  searchTerm }) {       edges {         node {           id           uri           ... on Page {             title           }           ... on Post {             title             excerpt           }         }       }       pageInfo {         hasNextPage         endCursor       }     }   } `

Our solution seems to work but let’s remember that the underlying mechanism that actually does the search for us is the native WordPress search query. And the WordPress default search function isn’t great. Its problems are limited search fields (in particular, taxonomies are not taken into account), no fuzzy matching, no control over the order of results. Big websites can also suffer from performance issues — there is no prebuilt search index, and the search query is performed directly on the website SQL database.

There are a few WordPress plugins that enhance the default search. Plugins like WP Extended Search add the ability to include selected meta keys and taxonomies in search queries.

The Relevanssi plugin replaces the standard WordPress search with its search engine using the full-text indexing capabilities of the database. Relevanssi deactivates the default search query which breaks the WPGraphQL where: {search : …}. There is some work already done on enabling Relevanssi search through WPGraphQL; the code might not be compatible with the latest WPGraphQL version, but it seems to be a good start for those who opt for Relevanssi search.

In the second part of this article, we’ll take one more possible path and have a closer look at the premium service from Jetpack — an advanced search powered by Elasticsearch. By the way, Jetpack Instant search is the solution adopted by CSS-Tricks.

Using Jetpack Instant Search with Gatsby

Jetpack Search is a per-site premium solution by Jetpack. Once installed and activated, it will take care of building an Elasticsearch index. The search queries no longer hit the SQL database. Instead, the search query requests are sent to the cloud Elasticsearch server, more precisely to:

https://public-api.wordpress.com/rest/v1.3/sites/{your-blog-id}/search 

There are a lot of search parameters to specify within the URL above. In our case, we will add the following:

  • filter[bool][must][0][term][post_type]=post: We only need results that are posts here, simply because our Gatsby website is limited to post. In real-life use, you might need spend some time configuring the boolean queries.
  • size=10 sets the number of returned results (maximum 20).
  • with highlight_fields[0]=title, we get the title string (or a part of it) with the searchTerm within the <mark> tags.
  • highlight_fields[0]=content is the same as below but for the post’s content.

There are three more search parameters depending on the user’s action:

  • query: The search term from the search input, e.g. gallery
  • sort: how the results should be orderer, the default is by score "score_default" (relevance) but there is also "date_asc" (newest) and "date_desc" (oldest)
  • page_handle: something like the “after” cursor for paginated results. We only request 10 results at once, and we will have a “load more” button.

Now, let’s see how a successful response is structured:

{   total: 9,   corrected_query: false,   page_handle: false, // or a string it the total value > 10   results: [     {       _score: 196.51814,       fields: {         date: '2018-11-03 03:55:09',         'title.default': 'Block: Gallery',         'excerpt.default': '',         post_id: 1918,         // we can configure what fields we want to add here with the query search parameters       },       result_type: 'post',       railcar: {/* we will not use this data */},       highlight: {         title: ['Block: <mark>Gallery</mark>'],         content: [           'automatically stretch to the width of your <mark>gallery</mark>. ... A four column <mark>gallery</mark> with a wide width:',           '<mark>Gallery</mark> blocks have two settings: the number of columns, and whether or not images should be cropped',         ],       },     },     /* more results */   ],   suggestions: [], // we will not use suggestions here   aggregations: [], // nor the aggregations }

The results field provides an array containing the database post IDs. To display the search results within a Gatsby site, we need to extract the corresponding post nodes (in particular their uri ) from the Gatsby data layer. My approach is to implement an instant search with asynchronous calls to the rest API and intersect the results with those of the static GraphQL query that returns all post nodes.

Let’s start by building an instant search widget that communicates with the search API. Since this is not specific to Gatsby, let’s see it in action in this Pen:

Here, useDebouncedInstantSearch is a custom hook responsible for fetching the results from the Jetpack Search API. My solution uses the awesome-debounce-promise library that allows us to take some extra care of the fetching mechanism. An instant search responds to the input directly without waiting for an explicit “Go!” from the user. If I’m typing fast, the request may change several times before even the first response arrives. Thus, there might be some unnecessary network bandwidth waste. The awesome-debounce-promise waits a given time interval (say 300ms) before making a call to an API; if there is a new call within this interval, the previous one will never be executed. It also resolves only the last promise returned from the call — this prevents the concurrency issues.

Now, with the search results available, let’s move back to Gatsby and build another custom hook:

import {useStaticQuery, graphql} from "gatsby"  export const useJetpackSearch = (params) => {   const {     allWpPost: { nodes },   } = useStaticQuery(graphql`     query AllPostsQuery {       allWpPost {         nodes {           id           databaseId           uri           title           excerpt         }       }     }   `)   const { error, loading, data } = useDebouncedInstantSearch(params)   return {     error,     loading,     data: {       ...data,       // map the results       results: data.results.map(el => {         // for each result find a node that has the same databaseId as the result field post_id         const node = nodes.find(item => item.databaseId === el.fields.post_id)         return {           // spread the node           ...node,           // keep the highlight info           highlight: el.highlight         }       }),     }   } }

I will call the useJetpackSearch within <SearchResults />. The Gatsby-version of <SearchResults /> is almost identical as that in the Pen above. The differences are highlighted in the code block below. The hook useDebouncedInstantSearch is replaced by useJetpackSearch (that calls the former internally). There is a Gatsby Link that replaces h2 as well as el.fields["title.default"] and el.fields["excerpt.default"] are replaced by el.title and el.excerpt.

const SearchResults = ({ params, setParams }) => {   const { loading, error, data } = useJetpackSearch(params)   const { searchTerm } = params   if (error) {     return <p>Error - {error}</p>   }   return (     <section className="search-results">       {loading ? (         <p className="info">Searching posts .....</p>       ) : (         <>           {data.total !== undefined && (             <p>               Found {data.total} results for{" "}               {data.corrected_query ? (                 <>                   <del>{searchTerm}</del> <span>{data.corrected_query}</span>                 </>               ) : (                 <span>{searchTerm}</span>               )}             </p>           )}         </>       )}       {data.results?.length > 0 && (         <ul>           {data.results.map((el) => {             return (               <li key={el.id}>                 <Link to={el.uri}>                   {el.highlight.title[0]                     ? el.highlight.title.map((item, index) => (                         <React.Fragment key={index}>                           {parse(item)}                         </React.Fragment>                       ))                     : parse(el.title)}                 </Link>                 <div className="post-excerpt">                   {el.highlight.content[0]                     ? el.highlight.content.map((item, index) => (                         <div key={index}>{parse(item)}</div>                       ))                     : parse(el.excerpt)}                 </div>               </li>             );           })}         </ul>       )}       {data.page_handle && (         <button           type="button"           disabled={loading}           onClick={() => setParams({ pageHandle: data.page_handle })}         >           {loading ? "loading..." : "load more"}         </button>       )}     </section>   ) } 

You can find the complete code in this repo and see it in action in this demo. Note that I no longer source WordPress data from the generic WordPress demo used by Gatsby starter. I need to have a website with Jetpack Search activated.

Wrapping up

We’ve just seen two ways of dealing with search in headless WordPress. Besides a few Gatsby-specific technical details (like using Gatsby Browser API), you can implement both discussed approaches within other frameworks. We’ve seen how to make use of the native WordPress search. I guess that it is an acceptable solution in many cases.

But if you need something better, there are better options available. One of them is Jetpack Search. Jetpack Instant Search does a great job on CSS-Tricks and, as we’ve just seen, can work with headless WordPress as well. There are probably other ways of implementing it. You can also go further with the query configuration, the filter functionalities, and how you display the results.


The post Native Search vs. Jetpack Instant Search in Headless WordPress With Gatsby appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

Chapter 4: Search

Previously in web history…

After an influx of rapid browser development following the creation of the web, Mosaic becomes the popular choice. Recognizing the commercial potential of the web, a team at O’Reilly builds GNN, the first commercial website. With something to browse with, and something to browse for, more and more people begin to turn to the web. Many create small, personal sites of their own. The best the web has to offer becomes almost impossible to find.

eBay had had enough of these spiders. They were fending them off by the thousands. Their servers buzzed with nonstop activity; a relentless stream of trespassers. One aggressor, however, towered above the rest. Bidder’s Edge, which billed itself as an auction aggregator, would routinely crawl the pages of eBay to extract its content and list it on its own site alongside other auction listings.

The famed auction site had unsuccessfully tried blocking Bidder’s Edge in the past. Like an elaborate game of Whac-A-Mole, they would restrict the IP address of a Bidder’s Edge server, only to be breached once again by a proxy server with a new one. Technology had failed. Litigation was next.

eBay filed suit against Bidder’s Edge in December of 1999, citing a handful of causes. That included “an ancient trespass theory known to legal scholars as trespass to chattels, basically a trespass or interference with real property — objects, animals, or, in this case, servers.” eBay, in other words, was arguing that Bidder’s Edge was trespassing — in the most medieval sense of that word — on their servers. In order for it to constitute trespass to chattels, eBay had to prove that the trespassers were causing harm. That their servers were buckling under the load, they argued, was evidence of that harm.

eBay in 1999

Judge Ronald M. Whyte found that last bit compelling. Quite a bit of back and forth followed, in one of the strangest lawsuits of a new era that included the phrase “rude robots” entering the official court record. These robots — as opposed to the “polite” ones — ignored eBay’s requests to block spidering on their sites, and made every attempt to circumvent counter measures. They were, by the judge’s estimation, trespassing. Whyte granted an injunction to stop Bidder’s Edge from crawling eBay until it was all sorted out.

Several appeals and countersuits and counter-appeals later, the matter was settled. Bidder’s Edge paid eBay an undisclosed amount and promptly shut their doors. eBay had won this particular battle. They had gotten rid of the robots. But the actual war was already lost. The robots — rude or otherwise — were already here.


If not for Stanford University, web search may have been lost. It is the birthplace of Yahoo!, Google and Excite. It ran the servers that ran the code that ran the first search engines. The founders of both Yahoo! and Google are alumni. But many of the most prominent players in search were not in the computer science department. They were in the symbolic systems program.

Symbolic systems was created at Stanford in 1985 as a study of the “relationship between natural and artificial systems that represent, process, and act on information.” Its interdisciplinary approach is rooted at the intersection of several fields: linguistics, mathematics, semiotics, psychology, philosophy, and computer science.

These are the same fields of study one would find at the heart of artificial intelligence research in the second half of the 20ᵗʰ century. But this isn’t the A.I. in its modern smart home manifestation, but in the more classical notion conceived by computer scientists as a roadmap to the future of computing technology. It is the understanding of machines as a way to augment the human mind. That parallel is not by accident. One of the most important areas of study at the symbolics systems program is artificial intelligence.

Numbered among the alumni of the program are several of the founders of Excite and Srinija Srinivasan, the fourth employee at Yahoo!. Her work in artificial intelligence led to a position at the ambitious A.I. research lab Cyc right out of college.

Marisa Mayer, an early employee at Google and, later, Yahoo!’s CEO, also drew on A.I. research during her time in the symbolic systems program. Her groundbreaking thesis project used natural language processing to help its users find the best flights through a simple conversation with a computer. “You look at how people learn, how people reason, and ask a computer to do the same things. It’s like studying the brain without the gore,” she would later say of the program.

Marissa Mayer in 1999

Search on the web stems from this one program at one institution at one brief moment in time. Not everyone involved in search engines studied that program — the founders of both Yahoo! and Google, for instance, were graduate students of computer science. But the ideology of search is deeply rooted in the tradition of artificial intelligence. The goal of search, after all, is to extract from the brain a question, and use machines to provide a suitable answer.

At Yahoo!, the principles of artificial intelligence acted as a guide, but it would be aided by human perspective. Web crawlers, like Excite, would bear the burden of users’ queries and attempt to map websites programmatically to provide intelligent results.

However, it would be at Google that A.I. would become an explicitly stated goal. Steven Levy, who wrote the authoritative book on the history of Google,https://bookshop.org/books/in-the-plex-how-google-thinks-works-and-shapes-our-lives/9781416596585 In the Plex, describes Google as a “vehicle to realize the dream of artificial intelligence in augmenting humanity.” Founders Larry Page and Sergey Brin would mention A.I. constantly. They even brought it up in their first press conference.

The difference would be a matter of approach. A tension that would come to dominate search for half a decade. The directory versus the crawler. The precision of human influence versus the completeness of machines. Surfers would be on one side and, on the other, spiders. Only one would survive.


The first spiders were crude. They felt around in the dark until they found the edge of the web. Then they returned home. Sometimes they gathered little bits of information about the websites they crawled. In the beginning, they gathered nothing at all.

One of the earliest web crawlers was developed at MIT by Matthew Gray. He used his World Wide Wanderer to go and find every website on the web. He wasn’t interested in the content of those sites, he merely wanted to count them up. In the summer of 1993, the first time he sent his crawler out, it got to 130. A year later, it would count 3,000. By 1995, that number grew to just shy of 30,000.

Like many of his peers in the search engine business, Gray was a disciple of information retrieval, a subset of computer science dedicated to knowledge sharing. In practice, information retrieval often involves a robot (also known as “spiders, crawlers, wanderers, and worms”) that crawls through digital documents and programmatically collects their contents. They are then parsed and stored in a centralized “index,” a shortcut that eliminates the need to go and crawl every document each time a search is made. Keeping that index up to date is a constant struggle, and robots need to be vigilant; going back out and re-crawling information on a near constant basis.

The World Wide Web posed a problematic puzzle. Rather than a predictable set of documents, a theoretically infinite number of websites could live on the web. These needed to be stored in a central index —which would somehow be kept up to date. And most importantly, the content of those sites needed to be connected to whatever somebody wanted to search, on the fly and in seconds. The challenge proved irresistible for some information retrieval researchers and academics. People like Jonathan Fletcher.

Fletcher, a former graduate and IT employee at the University of Stirling in Scotland, didn’t like how hard it was to find websites. At the time, people relied on manual lists, like the WWW Virtual Library maintained at CERN, or Mosaic’s list ofhttps://css-tricks.com/chapter-3-the-website/ “What’s New” that they updated daily. Fletcher wanted to handle it differently. “With a degree in computing science and an idea that there had to be a better way, I decided to write something that would go and look for me.”

He built Jumpstation in 1993, one of the earliest examples of a searchable index. His crawler would go out, following as many links as it could, and bring them back to a searchable, centralized database. Then it would start over. To solve for the issue of the web’s limitless vastness, Fletcher began by crawling only the titles and some metadata from each webpage. That kept his index relatively small, but but it also restricted search to the titles of pages.

Fletcher was not alone. After tinkering for several months, WebCrawler launched in April of 1994 out of the University of Washington. It holds the distinction of being the first search engine to crawl entire webpages and make them searchable. By November of that year, WebCrawler had served 1 million queries. At Carnegie Mellon, Michael Maudlin released his own spider-based search engine variant named for the Latin translation of wolf spider, Lycos. By 1995, it had indexed over a million webpages.

Search didn’t stay in universities long. Search engines had a unique utility for wayward web users on the hunt for the perfect site. Many users started their web sessions on a search engine. Netscape Navigator — the number one browser for new web users — connected users directly to search engines on their homepage. Getting listed by Netscape meant eyeballs. And eyeballs meant lucrative advertising deals.

In the second half of the 1990’s, a number of major players entered the search engine market. InfoSeek, initially a paid search option, was picked up by Disney, and soon became the default search engine for Netscape. AOL swooped in and purchased WebCrawler as part of a bold strategy to remain competitive on the web. Lycos was purchased by a venture capitalist who transformed it into a fully commercial enterprise.

Excite.com, another crawler started by Stanford alumni and a rising star in the search engine game for its depth and accuracy of results, was offered three million dollars not long after they launched. Its six co-founders lined up two couches, one across from another, and talked it out all night. They decided to stick with the product and bring in a new CEO. There would be many more millions to be made.

Excite in 1996

AltaVista, already a bit late to the game at the end of 1995, was created by the Digital Equipment Corporation. It was initially built to demonstrate the processing power of DEC computers. They quickly realized that their multithreaded crawler was able to index websites at a far quicker rate than their competitors. AltaVista would routinely deploy its crawlers — what one researcher referred to as a “brood of spiders” — to index thousands of sites at a time.

As a result, AltaVista was able to index virtually the entire web, nearly 10 million webpages at launch. By the following year, in 1996, they’d be indexing over 100 million. Because of the efficiency and performance of their machines, AltaVista was able to solve the scalability problem. Unlike some of their predecessors, they were able to make the full content of websites searchable, and they re-crawled sites every few weeks, a much more rapid pace than early competitors, who could take months to update their index. They set the standard for the depth and scope of web crawlers.

AltaVista in 1996

Never fully at rest, AltaVista used its search engine as a tool for innovation, experimenting with natural language processing, translation tools, and multi-lingual search. They were often ahead of their time, offering video and image search years before that would come to be an expected feature.

Those spiders that had not been swept up in the fervor couldn’t keep up. The universities hosting the first search engines were not at all pleased to see their internet connections bloated with traffic that wasn’t even related to the university. Most universities forced the first experimental search engines, like Jumpstation, to shut down. Except, that is, at Stanford.


Stanford’s history with technological innovation begins in the second half of the 20th century. The university was, at that point, teetering on the edge of becoming a second-tier institution. They had been losing ground and lucrative contracts to their competitors on the East Coast. Harvard and MIT became the sites of a groundswell of research in the wake of World War II. Stanford was being left behind.

In 1951, in a bid to reverse course on their downward trajectory, Dean of Engineering Frederick Terman brokered a deal with the city of Palo Alto. Stanford University agreed to annex 700 acres of land for a new industrial park that upstart companies in California could use. Stanford would get proximity to energetic innovation. The businesses that chose to move there would gain unique access to the Stanford student body for use on their product development. And the city of Palo Alto would get an influx of new taxes.

Hewlett-Packard was one of the first companies to move in. They ushered in a new era of computing-focused industry that would soon be known as Silicon Valley. The Stanford Research Park (later renamed Stanford Industrial Park) would eventually host Xerox during a time of rapid success and experimentation. Facebook would spend their nascent years there, growing into the behemoth it would become. At the center of it all was Stanford.

The research park transformed the university from one of stagnation to a site of entrepreneurship and cutting-edge technology. It put them at the heart of the tech industry. Stanford would embed itself — both logistically and financially — in the crucial technological developments of the second half of the 20ᵗʰ century, including the internet and the World Wide Web.

The potential success of Yahoo!, therefore, did not go unnoticed.


Jerry Yang and David Filo were not supposed to be working on Yahoo!. They were, however, supposed to be working together. They had met years ago, when David was Jerry’s teaching assistant in the Stanford computer science program. Yang eventually joined Filo as a graduate student and — after building a strong rapport — they soon found themselves working on a project together.

As they crammed themselves into a university trailer to begin working through their doctoral project, their relationship become what Yang has often described as perfectly balanced. “We’re both extremely tolerant of each other, but extremely critical of everything else. We’re both extremely stubborn, but very unstubborn when it comes to just understanding where we need to go. We give each other the space we need, but also help each other when we need it.”

In 1994, Filo showed Yang the web. In just a single moment, their focus shifted. They pushed their intended computer science thesis to the side, procrastinating on it by immersing themselves into the depths of the World Wide Web. Days turned into weeks which turned into months of surfing the web and trading links. The two eventually decided to combine their lists in a single place, a website hosted on their Stanford internet connection. It was called Jerry and David’s Guide to the World Wide Web, launched first to Stanford students in 1993 and then to the world in January of 1994. As catchy as that name wasn’t, the idea (and traffic) took off as friends shared with other friends.

Jerry and David’s Guide was a directory. Like the virtual library started at CERN, Yang and Filo organized websites into various categories that they made up on the fly. Some of these categories had strange or salacious names. Others were exactly what you might expect. When one category got too big, they split it apart. It was ad-hoc and clumsy, but not without charm. Through their classifications, Yang and Filo had given their site a personality. Their personality. In later years, Yang would commonly refer to this as the “voice of Yahoo!”

That voice became a guide — as the site’s original name suggested — for new users of the web. Their web crawling competitors were far more adept at the art of indexing millions of sites at a time. Yang and Filo’s site featured only a small subset of the web. But it was, at least by their estimation, the best of what the web had to offer. It was the cool web. It was also a web far easier to navigate than ever before.

Jerry Yang (left) and David Filo (right) in 1995 (Yahoo, via Flickr)

At the end of 1994, Yang and Filo renamed their site to Yahoo! (an awkward forced acronym for Yet Another Hierarchical Officious Oracle). By then, they were getting almost a hundred thousand hits a day, sometimes temporarily taking down Stanford’s internet in the process. Most other universities would have closed down the site and told them to get back to work. But not Stanford. Stanford had spent decades preparing for on-campus businesses just like this one. They kept the server running, and encouraged its creators to stake their own path in Silicon Valley.

Throughout 1994, Netscape had included Yahoo! in their browser. There was a button in the toolbar labeled “Net Directory” that linked directly to Yahoo!. Marc Andreessen, believing in the site’s future, agreed to host their website on Netscape’s servers until they were able to get on steady ground.

Yahoo! homepage in Netscape Navigator, circa 1994

Yang and Filo rolled up their sleeves, and began talking to investors. It wouldn’t take long. By the spring of 1996, they would have a new CEO and hold their own record-setting IPO, outstripping even their gracious host, Netscape. By then, they became the most popular destination on the web by a wide margin.

In the meantime, the web had grown far beyond the grasp of two friends swapping links. They had managed to categorize tens of thousands of sites, but there were hundreds of thousands more to crawl. “I picture Jerry Yang as Charlie Chaplin in Modern Times,” one journalist described, “confronted with an endless stream of new work that is only increasing in speed.” The task of organizing sites would have to go to somebody else. Yang and Filo found help in a fellow Stanford alumni, someone they had met years ago while studying abroad together in Japan, Srinija Srinivasan, a graduate of the symbolic systems program. Many of the earliest hires at Yahoo! were given slightly absurd titles that always ended in “Yahoo.” Yang and Filo went by Chief Yahoos. Srinivasan’s job title was Ontological Yahoo.

That is a deliberate and precise job title, and it was not selected by accident. Ontology is the study of being, an attempt to break the world into its component parts. It has manifested in many traditions throughout history and the world, but it is most closely associated with the followers of Socrates, in the work of Plato, and later in the groundbreaking text Metaphysics, written by Aristotle. Ontology asks the question “What exists?”and uses it as a thought experiment to construct an ideology of being and essence.

As computers blinked into existence, ontology found a new meaning in the emerging field of artificial intelligence. It was adapted to fit the more formal hierarchical categorizations required for a machine to see the world; to think about the world. Ontology became a fundamental way to describe the way intelligent machines break things down into categories and share knowledge.

The dueling definitions of the ontology of metaphysics and computer science would have been familiar to Srinija Srinivasan from her time at Stanford. The combination of philosophy and artificial intelligence in her studies gave her a unique perspective on hierarchical classifications. It was this experience that she brought to her first job after college at the Cyc Project, an artificial intelligence research lab with a bold project: to teach a computer common sense.

Srinija Srinivasan (Getty Images/James D. Wilson)

At Yahoo!, her task was no less bold. When someone looked for something on the site, they didn’t want back a random list of relevant results. They wanted the result they were actually thinking about, but didn’t quite know how to describe. Yahoo! had to — in a manner of seconds — figure out what its users really wanted. Much like her work in artificial intelligence, Srinivasan needed to teach Yahoo! how to think about a query and infer the right results.

To do that, she would need to expand the voice of Yahoo! to thousands of more websites in dozens of categories and sub-categories without losing the point of view established by Jerry and David. She would need to scale that perspective. “This is not a perfunctory file-keeping exercise. This is defining the nature of being,” she once said of her project. “Categories and classifications are the basis for each of our worldviews.”

At a steady pace, she mapped an ontology of human experience onto the site. She began breaking up the makeshift categories she inherited from the site’s creators, re-constituting them into more concrete and findable indexes. She created new categories and destroyed old ones. She sub-divided existing subjects into new, more precise ones. She began cross-linking results so that they could live within multiple categories. Within a few months she had overhauled the site with a fresh hierarchy.

That hierarchical ontology, however, was merely a guideline. The strength of Yahoo!’s expansion lay in the 50 or so content managers she had hired in the meantime. They were known as surfers. Their job was to surf the web — and organize it.

Each surfer was coached in the methodology of Yahoo! but were left with a surprising amount of editorial freedom. They cultivated the directory with their own interests, meticulously deliberating over websites and where they belong. Each decision could be strenuous, and there were missteps and incorrectly categorized items along the way. But by allowing individual personality to dictate hierarchal choices, Yahoo! retained its voice.

They gathered as many sites as they could, adding hundreds each day. Yahoo! surfers did not reveal everything on the web to their site’s visitors. They showed them what was cool. And that meant everything to users grasping for the very first time what the web could do.


At the end of 1995, the Yahoo! staff was watching their traffic closely. Huddled around consoles, employees would check their logs again and again, looking for a drop in visitors. Yahoo! had been the destination for the “Internet Directory” button on Netscape for years. It had been the source of their growth and traffic. Netscape had made the decision, at the last minute (and seemingly at random), to drop Yahoo!, replacing them with the new kids on the block, Excite.com. Best case scenario: a manageable drop. Worst case: the demise of Yahoo!.

But the drop never came. A day went by, and then another. And then a week. And then a few weeks. And Yahoo! remained the most popular website. Tim Brady, one of Yahoo!’s first employees, describes the moment with earnest surprise. “It was like the floor was pulled out in a matter of two days, and we were still standing. We were looking around, waiting for things to collapse in a lot of ways. And we were just like, I guess we’re on our own now.”

Netscape wouldn’t keep their directory button exclusive for long. By 1996, they would begin allowing other search engines to be listed on their browser’s “search” feature. A user could click a button and a drop-down of options would appear, for a fee. Yahoo! bought themselves back in to the drop-down. They were joined by four other search engines, Lycos, InfoSeek, Excite, and AltaVista.

By that time, Yahoo! was the unrivaled leader. It had transformed its first mover advantage into a new strategy, one bolstered by a successful IPO and an influx of new investment. Yahoo! wanted to be much more than a simple search engine. Their site’s transformation would eventually be called a portal. It was a central location for every possible need on the web. Through a number of product expansions and aggressive acquisitions, Yahoo! released a new suite of branded digital products. Need to send an email? Try Yahoo! Mail. Looking to create website? There’s Yahoo! Geocities. Want to track your schedule? Use Yahoo! Calendar. And on and on the list went.

Yahoo! in 1996

Competitors rushed the fill the vacuum of the #2 slot. In April of 1996, Yahoo!, Lycos and Excite all went public to soaring stock prices. Infoseek had their initial offering only a few months later. Big deals collided with bold blueprints for the future. Excite began positioning itself as a more vibrant alternative to Yahoo! with more accurate search results from a larger slice of the web. Lycos, meanwhile, all but abounded the search engine that had brought them initial success to chase after the portal-based game plan that had been a windfall for Yahoo!.

The media dubbed the competition the “portal wars,” a fleeting moment in web history when millions of dollars poured into a single strategy. To be the biggest, best, centralized portal for web surfers. Any service that offered users a destination on the web was thrown into the arena. Nothing short of the future of the web (and a billion dollar advertising industry) was at stake.

In some ways, though, the portal wars were over before they started. When Excite announced a gigantic merger with @Home, an Internet Service Provider, to combine their services, not everyone thought it was a wise move. “AOL and Yahoo! were already in the lead,” one investor and cable industry veteran noted, “and there was no room for a number three portal.” AOL had just enough muscle and influence to elbow their way into the #2 slot, nipping at the heels of Yahoo!. Everyone else would have to go toe-to-toe with Goliath. None were ever able to pull it off.

Battling their way to market dominance, most search engines had simply lost track of search. Buried somewhere next to your email and stock ticker and sports feed was, in most cases, a second rate search engine you could use to find things — only not often and not well. That’s is why it was so refreshing when another search engine out of Stanford launched with just a single search box and two buttons, its bright and multicolored logo plastered across the top.


A few short years after it launched, Google was on the shortlist of most popular sites. In an interview with PBS Newshour in 2002, co-founder Larry Page described their long-term vision. “And, actually, the ultimate search engine, which would understand, you know, exactly what you wanted when you typed in a query, and it would give you the exact right thing back, in computer science we call that artificial intelligence.”

Google could have started anywhere. It could have started with anything. One employee recalls an early conversation with the site’s founders where he was told “we are not really interested in search. We are making an A.I.” Larry Page and Sergey Brin, the creators of Google, were not trying to create the web’s greatest search engine. They were trying to create the web’s most intelligent website. Search was only their most logical starting point.

Imprecise and clumsy, the spider-based search engines of 1996 faced an uphill battle. AltaVista had proved that the entirety of the web, tens of millions of webpages, could be indexed. But unless you knew your way around a few boolean logic commands, it was hard to get the computer to return the right results. The robots were not yet ready to infer, in Page’s words, “exactly what you wanted.”

Yahoo! had filled in these cracks of technology with their surfers. The surfers were able to course-correct the computers, designing their directory piece by piece rather than relying on an algorithm. Yahoo! became an arbiter of a certain kind of online chic; tastemakers reimagined for the information age. The surfers of Yahoo! set trends that would last for years. Your site would live or die by their hand. Machines couldn’t do that work on their own. If you wanted your machines to be intelligent, you needed people to guide them.

Page and Brin disagreed. They believed that computers could handle the problem just fine. And they aimed to prove it.

That unflappable confidence would come to define Google far more than their “don’t be evil” motto. In the beginning, their laser-focus on designing a different future for the web would leave them blind to the day-to-day grind of the present. On not one, but two occasions, checks made out to the company for hundreds of thousands of dollars were left in desk drawers or car trunks until somebody finally made the time to deposit them. And they often did things different. Google’s offices, for instances, were built to simulate a college dorm, an environment the founders felt most conducive to big ideas.

Google would eventually build a literal empire on top of a sophisticated, world-class infrastructure of their own design, fueled by the most elaborate and complex (and arguably invasive) advertising mechanism ever built. There are few companies that loom as large as Google. This one, like others, started at Stanford.


Even among the most renowned artificial intelligence experts, Terry Winograd, a computer scientist and Stanford professor, stands out in the crowd. He was also Larry Page’s advisor and mentor when he was a graduate student in the computer science department. Winograd has often recalled the unorthodox and unique proposals he would receive from Page for his thesis project, some of which involved “space tethers or solar kites.” “It was science fiction more than computer science,” he would later remark.

But for all of his fanciful flights of imagination, Page always returned to the World Wide Web. He found its hyperlink structure mesmerizing. Its one-way links — a crucial ingredient in the web’s success — had led to a colossal proliferation of new websites. In 1996, when Page first began looking at the web, there were tens of thousands of sites being added every week. The master stroke of the web was to enable links that only traveled in one direction. That allowed the web to be decentralized, but without a central database tracking links, it was nearly impossible to collect a list of all of the sites that linked to a particular webpage. Page wanted to build a graph of who was linking to who; an index he could use to cross-reference related websites.

Page understood that the hyperlink was a digital analog to academic citations. A key indicator of the value of a particular academic paper is the amount of times it has been cited. If a paper is cited often (by other high quality papers), it is easier to vouch for its reliability. The web works the same way. The more often your site is linked to (what’s known as a backlink), the more dependable and accurate it is likely to be.

Theoretically, you can determine the value of a website by adding up all of the other websites that link to it. That’s only one layer though. If 100 sites link back to you, but each of them has only ever been linked to one time, that’s far less valuable than if five sites that each have been linked to 100 times link back to you. So it’s not simply how many links you have, but the quality of those links. If you take both of those dimensions and aggregate sites using backlinks as a criteria, you can very quickly start to assemble a list of sites ordered by quality.

John Battelle describes the technical challenge facing Page in his own retelling of the Google story, The Search.

Page realized that a raw count of links to a page would be a useful guide to that page’s rank. He also saw that each link needed its own ranking, based on the link count of its originating page. But such an approach creates a difficult and recursive mathematical challenge — you not only have to count a particular page’s links, you also have to count the links attached to the links. The math gets complicated rather quickly.

Fortunately, Page already knew a math prodigy. Sergey Brin had proven his brilliance to the world a number of times before he began a doctoral program in the Stanford computer science department. Brin and Page had crossed paths on several occasions, a relationship that began on rocky ground but grew towards mutual respect. The mathematical puzzle at the center of Page’s idea was far too enticing for Brin to pass up.

He got to work on a solution. “Basically we convert the entire Web into a big equation, with several hundred million variables,” he would later explain, “which are the page ranks of all the Web pages, and billions of terms, which are the links. And we’re able to solve that equation.” Scott Hassan, the seldom talked about third co-founder of Google who developed their first web crawler, summed it up a bit more concisely, describing Google’s algorithm as an attempt to “surf the web backward!”

The result was PageRank — as in Larry Page, not webpage. Brin, Page, and Hassan developed an algorithm that could trace backlinks of a site to determine the quality of a particular webpage. The higher value of a site’s backlinks, the higher up the rankings it climbed. They had discovered what so many others had missed. If you trained a machine on the right source — backlinks — you could get remarkable results.

It was only after that that they began matching their rankings to search queries when they realized PageRank fit best in a search engine. They called their search engine Google. It was launched on Stanford’s internet connection in August of 1996.

Google in 1998

Google solved the relevancy problem that had plagued online search since its earliest days. Crawlers like Lycos, AltaVista and Excite were able to provide a list of webpages that matched a particular search. They just weren’t able to sort them right, so you had to go digging to find the result you wanted. Google’s rankings were immediately relevant. The first page of your search usually had what you needed. They were so confident in their results they added an “I’m Feeling Lucky” button which took users directly to the first result for their search.

Google’s growth in their early days was not unlike Yahoo!’s in theirs. They spread through word of mouth, from friends to friends of friends. By 1997, they had grown big enough to put a strain on the Stanford network, something Yang and Filo had done only a couple of years earlier. Stanford once again recognized possibility. It did not push Google off their servers. Instead, Stanford’s advisors pushed Page and Brin in a commercial direction.

Initially, the founders sought to sell or license their algorithm to other search engines. They took meetings with Yahoo!, Infoseek and Excite. No one could see the value. They were focused on portals. In a move that would soon sound absurd, they each passed up the opportunity to buy Google for a million dollars or less, and Page and Brin could not find a partner that recognized their vision.

One Stanford faculty member was able to connect them with a few investors, including Jeff Bezos and David Cheriton (which got them those first few checks that sat in a desk drawer for weeks). They formally incorporated in September of 1998, moving into a friend’s garage, bringing a few early employees along, including symbolics systems alumni Marissa Mayer.

Larry Page (left) and Sergey Brin (right) started Google in a friend’s garage.

Even backed by a million dollar investment, the Google founders maintained a philosophy of frugality, simplicity, and swiftness. Despite occasional urging from their investors, they resisted the portal strategy and remained focused on search. They continued tweaking their algorithm and working on the accuracy of their results. They focused on their machines. They wanted to take the words that someone searched for and turn them into something actually meaningful. If you weren’t able to find the thing you were looking for in the top three results, Google had failed.

Google was followed by a cloud of hype and positive buzz in the press. Writing in Newsweek, Steven Levy described Google as a “high-tech version of the Oracle of Delphi, positioning everyone a mouse click away from the answers to the most arcane questions — and delivering simple answers so efficiently that the process becomes addictive.” It was around this time that “googling” — a verb form of the site synonymous with search — entered the common vernacular. The portal wars were still waging, but Google was poking its head up as a calm, precise alternative to the noise.

At the end of 1998, they were serving up ten thousand searches a day. A year later, that would jump to seven million a day. But quietly, behind the scenes, they began assembling the pieces of an empire.

As the web grew, technologists and journalists predicted the end of Google; they would never be able to keep up. But they did, outlasting a dying roster of competitors. In 2001, Excite went bankrupt, Lycos closed down, and Disney suspended Infoseek. Google climbed up and replaced them. It wouldn’t be until 2006 that Google would finally overtake Yahoo! as the number one website. But by then, the company would transform into something else entirely.

After securing another round of investment in 1999, Google moved into their new headquarters and brought on an army of new employees. The list of fresh recruits included former engineers at AltaVista, and leading artificial intelligence expert Peter Norving. Google put an unprecedented focus on advancements in technology. Better servers. Faster spiders. Bigger indexes. The engineers inside Google invented a web infrastructure that had, up to that point, been only theoretical.

They trained their machines on new things, and new products. But regardless of the application, translation or email or pay-per-click advertising, they rested on the same premise. Machines can augment and re-imagine human intelligence, and they can do it at limitless scale. Google took the value proposition of artificial intelligence and brought it into the mainstream.

In 2001, Page and Brin brought in Silicon Valley veteran Eric Schmidt to run things as their CEO, a role he would occupy for a decade. He would oversee the company during its time of greatest growth and innovation. Google employee #4 Heather Cairns recalls his first days on the job. “He did this sort of public address with the company and he said, ‘I want you to know who your real competition is.’ He said, ‘It’s Microsoft.’ And everyone went, What?

Bill Gates would later say, “In the search engine business, Google blew away the early innovators, just blew them away.” There would come a time when Google and Microsoft would come face to face. Eric Schmidt was correct about where Google was going. But it would take years for Microsoft to recognize Google as a threat. In the second half of the 1990’s, they were too busy looking in their rearview mirror at another Silicon Valley company upstart that had swept the digital world. Microsoft’s coming war with Netscape would subsume the web for over half a decade.


The post Chapter 4: Search appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

Using Structured Data to Enhance Search Engine Optimization

SEO is often considered the snake oil of the web. How many times have you scrolled through attention-grabbing headlines on know how to improve your SEO? Everyone and their uncle seems to have some “magic” cure to land high in search results and turn impressions into conversions. Sifting through so much noise on the topic can cause us to miss true gems that might be right under our nose.

We’re going to look at one such gem in this article: structured data.

There’s a checklist of SEO must-haves that we know are needed when working on a site. It includes things like a strong <title>, a long list of <meta> tags, and using descriptive alt tags on images (which is a double win for accessibility). Running a cursory check on any site using Lighthouse will flag up turn up even more tips and best practices to squeeze the most SEO out of the content.

Search engines are getting smarter, however, and starting to move past the algorithmic scraping techniques of yesteryear. Google, Amazon, and Microsoft are all known to be investing a considerable amount in machine learning, and with that, they need clean data to feed their search AI.

That’s where the the concept of schemas comes into play. In fact, it’s funding from Google and Microsoft — along with Yahoo and Yandex — that led to the establishment of schema.org, a website and community to push their format — more commonly referred to as structured data —forward so that they and other search engines can help surface content in more useful and engaging ways.

So, what is structured data?

Structured data describes the content of digital documents (i.e. websites, emails, etc). It’s used all over the web and, much like <meta> tags, is an invisible layer of information that search engines use to read the content.

Structured data comes in three flavors: Microdata, RDFa and JSON-LD. Microdata and RDF are both injected directly into the HTML elements of a document, peppering each relevant element of a page with machine readable pointers. For example, an example of using Microdata attributes on a product, taken straight from the schema.org docs:

<div itemscope itemtype="http://schema.org/Product">   <span itemprop="name">Kenmore White 17" Microwave</span>   <img itemprop="image" src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />   <div itemprop="aggregateRating"     itemscope itemtype="http://schema.org/AggregateRating">    Rated <span itemprop="ratingValue">3.5</span>/5    based on <span itemprop="reviewCount">11</span> customer reviews   </div>   <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">     <!--price is 1000, a number, with locale-specific thousands separator     and decimal mark, and the $  character is marked up with the     machine-readable code "USD" -->     <span itemprop="priceCurrency" content="USD">$ </span><span           itemprop="price" content="1000.00">1,000.00</span>     <link itemprop="availability" href="http://schema.org/InStock" />In stock   </div>   Product description:   <span itemprop="description">0.7 cubic feet countertop microwave.   Has six preset cooking categories and convenience features like   Add-A-Minute and Child Lock.</span>   Customer reviews:   <div itemprop="review" itemscope itemtype="http://schema.org/Review">     <span itemprop="name">Not a happy camper</span> -     by <span itemprop="author">Ellie</span>,     <meta itemprop="datePublished" content="2011-04-01">April 1, 2011     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">       <meta itemprop="worstRating" content = "1">       <span itemprop="ratingValue">1</span>/       <span itemprop="bestRating">5</span>stars     </div>     <span itemprop="description">The lamp burned out and now I have to replace     it. </span>   </div>   <div itemprop="review" itemscope itemtype="http://schema.org/Review">     <span itemprop="name">Value purchase</span> -     by <span itemprop="author">Lucas</span>,     <meta itemprop="datePublished" content="2011-03-25">March 25, 2011     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">       <meta itemprop="worstRating" content = "1"/>       <span itemprop="ratingValue">4</span>/       <span itemprop="bestRating">5</span>stars     </div>     <span itemprop="description">Great microwave for the price. It is small and     fits in my apartment.</span>   </div>   <!-- etc. --> </div>

If that seems like bloated markup, it kinda is. But it’s certainly beneficial if you prefer to consolidate all of your data in one place.

JSON-LD, on the other hand, usually sits in a <script> tag and describes the same properties in a single block of data. Again, from the docs:

<script type="application/ld+json"> {   "@context": "http://schema.org",   "@type": "Product",   "aggregateRating": {     "@type": "AggregateRating",     "ratingValue": "3.5",     "reviewCount": "11"   },   "description": "0.7 cubic feet countertop microwave. Has six preset cooking categories and convenience features like Add-A-Minute and Child Lock.",   "name": "Kenmore White 17" Microwave",   "image": "kenmore-microwave-17in.jpg",   "offers": {     "@type": "Offer",     "availability": "http://schema.org/InStock",     "price": "55.00",     "priceCurrency": "USD"   },   "review": [     {       "@type": "Review",       "author": "Ellie",       "datePublished": "2011-04-01",       "description": "The lamp burned out and now I have to replace it.",       "name": "Not a happy camper",       "reviewRating": {         "@type": "Rating",         "bestRating": "5",         "ratingValue": "1",         "worstRating": "1"       }     },     {       "@type": "Review",       "author": "Lucas",       "datePublished": "2011-03-25",       "description": "Great microwave for the price. It is small and fits in my apartment.",       "name": "Value purchase",       "reviewRating": {         "@type": "Rating",         "bestRating": "5",         "ratingValue": "4",         "worstRating": "1"       }     }   ] } </script>

This is my personal preference, as it is treated as a little external instruction manual for your content, much like JavaScript for scripts, and CSS for your styles, all happily self-contained. JSON-LD can become essential for certain types of schema, where the content of the page is different from the content of the structured data (for example, check out the speakable property, currently in beta).

A welcome introduction to the implementation of JSON-LD on the web is Google’s allowance of fetching structured data from an external source, rather than forcing inline scripting, which was previously frustratingly impossible. This can be done either by the developer, or in Google Tag Manager.

What structured data means to you

Beyond making life easier for search engine crawlers to read your pages? Two words: Rich snippets. Rich snippets are highly visual modules that tend to sit at the top of the search engine, in what is sometimes termed as “Position 0” in the results — displayed above the first search result. Here’s an example of a simple search for “blueberry pie” in Google as an example:

Google search results showing three recipes displayed as cards at the top, a card of nutritional facts in the right sidebar, a first result showing user reviews, and finally, the search results.
Check out those three recipes up top — and that content in the right column — showing up before the list of results using details from structured data.

Even the first result is a rich snippet! As you can see, using structured data is your ticket to get into a rich snippet on a search results page. And, not to spur FOMO or anything, but any site not showing up in a rich snippet is already at risk of dropping into “below the fold” territory. Notice how the second organic result barely makes the cut.

Fear not, dear developers! Adding and testing structured data to a website is aq simple and relatively painless process. Once you get the hang of it, you’ll be adding it to every possible location you can imagine, even emails.

It is worth noting that structured data is not the only way to get into rich snippets. Search engines can sometimes determine enough from your HTML to display some snippets, but utilizing it will push the odds in your favor. Plus, using structured data puts the power of how your content is displayed in your hands, rather than letting Google or the like determine it for you.

Types of structured data

Structured data is more than recipes. Here’s a full list of the types of structured data Google supports. (Spoiler alert: it’s almost any kind of content.)

  • Article
  • Book (limited support)
  • Breadcrumb
  • Carousel
  • Course
  • COVID-19 announcements (beta)
  • Critic review (limited support)
  • Dataset
  • Employer aggregate rating
  • Estimated salary
  • Event
  • Fact check
  • FAQ
  • How-to
  • Image license metadata (beta)
  • Job posting
  • Local business
  • Logo
  • Movie
  • Product
  • Q&A
  • Recipe
  • Review snippet
  • Sitelinks searchbox
  • Software app
  • Speakable (beta)
  • Subscription and paywalled content
  • Video

Yep, lots of options here! But with those come lots of opportunity to enhance a site’s content and leverage these search engine features.

Using structured data

The easiest way to find the right structured data for your project is to look through Google’s search catalogue. Advanced users may like to browse what’s on schema.org, but I’ll warn you that it is a scary rabbit hole to crawl through.

Let’s start with a fairly simple example: the Logo logo data type. It’s simple because all we really need is a website URL and the source URL for an image, along with some basic details to help search engine’s know they are looking at a logo. Here’s our JSON-LD:

<script type="application/ld+json">   {     "@context": "https://schema.org",     "@type": "Organization",     "name": "Example",     "url": "http://www.example.com",     "logo": "http://www.example.com/images/logo.png"   } </script>

First off, we have the <script> tag itself, telling search engines that it’s about to consume some JSON-LD.

From there, we have five properties:

  • @context: This is included on all structured data objects, no matter what type it is. It’s what tells search engines that the JSON-LD contains data that is defined by schema.org specifications.
  • @type: This is the reference type for the object. It’s used to identify what type of content we’re working with. In this case, it’s “Organization” which has a whole bunch of sub-properties that follow.
  • name: This is the sub-property that contains the organization’s name.
  • url: This is the sub-property that contains the organization’s web address.
  • logo: This is the sub-property that contains the file path for the organization’s logo image file. For Google to consider this, it must be at least 112⨉112px and in JPG, PNG, or GIF format. Sorry, no SVG at the moment.

A page can have multiple structured data types. That means it’s possible to mix and match content.

Testing structured data

See, dropping structured data into a page isn’t that tough, right? Once we have it, though, we should probably check to see if it actually works.

Google, Bing, and Yandex (login required) all have testing tools available. Google even has one specifically for validating structured data in email. In most cases, simply drop in the website URL and the tool will spin up a test and show which object it recognizes, the properties it sees, and any errors or warning to look into.

Showing Google's testing results where the JSON-LD is displayed on the left of the screen and the details of it on the right.
Google’s structured data testing tool fetches the markup and displays the information it recognizes.

The next step is to confirm that the structured data is accessible on your live site through Google Search Console. You may need to set up an account and verify your site in order to use a particular search engine’s console, but checking data is — yet again — as simple as dropping in a site URL and using the inspection tools to check that the site is indeed live and sending data when it is accessed by the search engine.

If the structured data is implemented correctly, it will display. In Google’s case, it’s located in the “Enhancements” section with a big ol’ checkmark next to it.

Google Search Console screenshot showing Google can find the site and that it recognizes search enhancements below that. In this case, it is showing that the Logo structured data type was found and is supported.
Notice the “Logo” that is detected at the end — it works!

But wait! I did all that and nothing’s happening… what gives?

As with all search engine optimizations, there are no guarantees or time scales, when it comes to how or when structured data is used. It might take a good while before rich snippets take hold for your content — days, weeks, or even months! I know, it stinks to be left in the dark like that. It is unfortunately a waiting game.


Hopefully, this gives you a good idea of what structured data is and how it can be used to leverage features that search engines have made to spotlight content has it.

There’s absolutely no shortage of advice, tips, and tricks for helping optimize a site for search engines. While so much of it is concerned with what’s contained in the <head> or how content is written, there are practical things that developers can do to make an impact. Structured data is definitely one of those things and worth exploring to get the most value from content. To know more about this, visit https://victoriousseo.com/case-studies/.

The world is your oyster with structured data. And, sure, while search engine only support a selection of the schema.org vocabulary, they are constantly evolving and extending that support. Why not start small by adding structured data to an email link in a newsletter? Or perhaps you’re into trying something different, like defining a sitelinks search box (which is very meta but very cool). Or, hey, add a recipe for Pinterest. Blueberry pie, anyone?

The post Using Structured Data to Enhance Search Engine Optimization appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Block Links: The Search for a Perfect Solution

I was reading this article by Chris where he talks about block links — you know, like wrapping an entire card element inside an anchor — being a bad idea. It’s bad accessibility because of how it affects screen readers. And it’s bad UX because it prevents simple user tasks, like selecting text.

But maybe there’s something else at play. Maybe it’s less an issue with the pattern than the implementation of it. That led me to believe that this is the time to write follow-up article to see if we can address some of the problems Chris pointed out.

Throughout this post, I’ll use the term “card” to describe a component using the block link pattern. Here’s what we mean by that.

Let’s see how we want our Card Components to work:

  1. The whole thing should be linked and clickable.
  2. It should be able to contain more than one link.
  3. Content should be semantic so assistive tech can understand it.
  4. The text should be selectable, like regular links.
  5. Things like right-click and keyboard shortcuts should work with it
  6. Its elements should be focusable when tabbing.

That’s a long list! And since we don’t have any standard card widget provided by the browser, we don’t have any standard guidelines to build it. 

Like most things on the web, there’s more than one way to make a card component. However, I haven’t found something that checks all the requirements we just covered. In this article, we will try to hit all of them. That’s what we’re going to do now!

Method 1: Wrap everything an <a>

This is the most common and the easiest way to make a linked card. Take the HTML for the card and wrap the entire thing in an anchor tag.

<a href="/">   <!-- Card markup --> </a>

Here’s what that gives us:

  1. It’s clickable.
  2. It works with right-click and keyboard shortcuts.

Well, not great. We still can’t:

  1. Put another link inside the card because the entire thing is a single link
  2. Use it with a screen reader — the content is not semantic, so assistive technology will announce everything inside the card, starting from the time stamp
  3. Select text

That’s enough 👎 that we probably shouldn’t use it. Let’s move onto the next technique.

Method 2: Just link what needs linking

This is a nice compromise that sacrifices a little UX for improved accessibility.

With this pattern we achieve most of our goals:

  1. We can put as many links as we want. 
  2. Content is semantic.
  3. We can select the text from Card.
  4. Right Click and keyboard shortcuts work.
  5. The focus is in proper order when tabbing.

But it is missing the main feature we want in a card: the whole thing should be clickable! Looks like we need to try some other way.

Method 3: The good ol’  ::before pseudo element

In this one, we add a ::before or ::after element, place it above the card with absolute positioning and stretch it over the entire width and height of the card so it’s clickable.

But now:

  1. We still can’t add more than one link because anything else that’s linked is under the pseudo element layer. We can try to put all the text above the pseudo element, but card link itself won’t work when clicking on top of the text.
  2. We still can’t select the text. Again, we could swap layers, but then we’re back to the clickable link issue all over again.

Let’s try to actually check all the boxes here in our final technique.

Method 4: Sprinkle JavaScript on the second method

Let’s build off the second method. Recall that’s what where we link up everything we want to be a link:

<article class="card">   <time datetime="2020-03-20">Mar 20, 2020</time>   <h2><a href="https://css-tricks.com/a-complete-guide-to-calc-in-css/" class="main-link">A Complete Guide to calc() in CSS</a></h2>   <p>     In this guide, let’s cover just about everything there is to know about this very useful function.   </p>   <a class="author-name" href="https://css-tricks.com/author/chriscoyier/" target="_blank">Chris Coyier</a>     <div class="tags">       <a class="tag" href="https://css-tricks.com/tag/calc/" >calc</a>     </div> </article>

So how do we make the whole card clickable? We could use JavaScript as a progressive enhancement to do that. We’ll start by adding a click event listener to the card and trigger the click on the main link when it is triggered.

const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') 
 card.addEventListener("click", handleClick) 
 function handleClick(event){   mainLink.click(); }

Temporarily, this introduces the problem that we can’t select the text, which we’ve been trying to fix this whole time. Here’s the trick: we’ll use the relatively less-known web API window.getSelection. From MDN:

The Window.getSelection() method returns a Selection object representing the range of text selected by the user or the current position of the caret.

Although, this method returns an Object, we can convert it to a string with toString().

const isTextSelected = window.getSelection().toString()

With one line and no complicated kung-fu tricks with event listeners, we know if the user has selected text. Let’s use that in our handleClick function.

const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') 
 card.addEventListener("click", handleClick) 
 function handleClick(event){   const isTextSelected = window.getSelection().toString();   if (!isTextSelected) {     mainLink.click();   } }

This way, the main link can be clicked when no text selected, and all it took was a few lines of JavaScript. This satisfies our requirements:

  1. The whole thing is linked and clickable.
  2. It is able to contain more than one link.
  3. This content is semantic so assistive tech can understand it.
  4. The text should be selectable, like regular links.
  5. Things like right-click and keyboard shortcuts should work with it
  6. Its elements should be focusable when tabbing.

Here’s the final demo with all the JavaScript code we have added:

I think we’ve done it! Now you know how to make a perfect clickable card component.

What about other patterns? For example, what if the card contains the excerpt of a blog post followed by a “Read More’ link? Where should that go? Does that become the “main” link? What about image?

For those questions and more, here’s some further reading on the topic:

The post Block Links: The Search for a Perfect Solution appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

How to Redirect a Search Form to a Site-Scoped Google Search

This is just a tiny little trick that might be helpful on a site where you don’t have the time or desire to build out a really good on-site search solution. Google.com itself can perform searches scoped to one particular site. The trick is getting people there using that special syntax without them even knowing it.

Make a search form:

<form action="https://google.com/search" target="_blank" type="GET">    <label>      Search CSS-Tricks on Google:       <input type="search" name="q">    </label>    <input type="submit" value="Go">  </form>

When that form is submitted, we’ll intercept it and change the value to include the special syntax:

var form = document.querySelector("form");  form.addEventListener("submit", function (e) {   e.preventDefault();   var search = form.querySelector("input[type=search]");   search.value = "site:css-tricks.com " + search.value;   form.submit(); }); 

That’s all.

The post How to Redirect a Search Form to a Site-Scoped Google Search appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]