Tag: Search

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.



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.

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.


, , , , , ,

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.


, , , ,

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.


, , , ,

How to Add Lunr Search to your Gatsby Website

The Jamstack way of thinking and building websites is becoming more and more popular.

Have you already tried Gatsby, Nuxt, or Gridsome (to cite only a few)? Chances are that your first contact was a “Wow!” moment — so many things are automatically set up and ready to use. 

There are some challenges, though, one of which is search functionality. If you’re working on any sort of content-driven site, you’ll likely run into search and how to handle it. Can it be done without any external server-side technology? 

Search is not one of those things that come out of the box with Jamstack. Some extra decisions and implementation are required.

Fortunately, we have a bunch of options that might be more or less adapted to a project. We could use Algolia’s powerful search-as-service API. It comes with a free plan that is restricted to non-commercial projects with  a limited capacity. If we were to use WordPress with WPGraphQL as a data source, we could take advantage of WordPress native search functionality and Apollo Client. Raymond Camden recently explored a few Jamstack search options, including pointing a search form directly at Google.

In this article, we will build a search index and add search functionality to a Gatsby website with Lunr, a lightweight JavaScript library providing an extensible and customizable search without the need for external, server-side services. We used it recently to add “Search by Tartan Name” to our Gatsby project tartanify.com. We absolutely wanted persistent search as-you-type functionality, which brought some extra challenges. But that’s what makes it interesting, right? I’ll discuss some of the difficulties we faced and how we dealt with them in the second half of this article.

Getting started

For the sake of simplicity, let’s use the official Gatsby blog starter. Using a generic starter lets us abstract many aspects of building a static website. If you’re following along, make sure to install and run it:

gatsby new gatsby-starter-blog https://github.com/gatsbyjs/gatsby-starter-blog cd gatsby-starter-blog gatsby develop

It’s a tiny blog with three posts we can view by opening up http://localhost:8000/___graphql in the browser.

Showing the GraphQL page on the localhost installation in the browser.

Inverting index with Lunr.js 🙃

Lunr uses a record-level inverted index as its data structure. The inverted index stores the mapping for each word found within a website to its location (basically a set of page paths). It’s on us to decide which fields (e.g. title, content, description, etc.) provide the keys (words) for the index.

For our blog example, I decided to include all titles and the content of each article. Dealing with titles is straightforward since they are composed uniquely of words. Indexing content is a little more complex. My first try was to use the rawMarkdownBody field. Unfortunately, rawMarkdownBody introduces some unwanted keys resulting from the markdown syntax.

Showing an attempt at using markdown syntax for links.

I obtained a “clean” index using the html field in conjunction with the striptags package (which, as the name suggests, strips out the HTML tags). Before we get into the details, let’s look into the Lunr documentation.

Here’s how we create and populate the Lunr index. We will use this snippet in a moment, specifically in our gatsby-node.js file.

const index = lunr(function () {   this.ref('slug')   this.field('title')   this.field('content')   for (const doc of documents) {     this.add(doc)   } })

 documents is an array of objects, each with a slug, title and content property:

{   slug: '/post-slug/',   title: 'Post Title',   content: 'Post content with all HTML tags stripped out.' }

We will define a unique document key (the slug) and two fields (the title and content, or the key providers). Finally, we will add all of the documents, one by one.

Let’s get started.

Creating an index in gatsby-node.js 

Let’s start by installing the libraries that we are going to use.

yarn add lunr graphql-type-json striptags

Next, we need to edit the gatsby-node.js file. The code from this file runs once in the process of building a site, and our aim is to add index creation to the tasks that Gatsby executes on build. 

CreateResolvers is one of the Gatsby APIs controlling the GraphQL data layer. In this particular case, we will use it to create a new root field; Let’s call it LunrIndex

Gatsby’s internal data store and query capabilities are exposed to GraphQL field resolvers on context.nodeModel. With getAllNodes, we can get all nodes of a specified type:

/* gatsby-node.js */ const { GraphQLJSONObject } = require(`graphql-type-json`) const striptags = require(`striptags`) const lunr = require(`lunr`)  exports.createResolvers = ({ cache, createResolvers }) => {   createResolvers({     Query: {       LunrIndex: {         type: GraphQLJSONObject,         resolve: (source, args, context, info) => {           const blogNodes = context.nodeModel.getAllNodes({             type: `MarkdownRemark`,           })           const type = info.schema.getType(`MarkdownRemark`)           return createIndex(blogNodes, type, cache)         },       },     },   }) }

Now let’s focus on the createIndex function. That’s where we will use the Lunr snippet we mentioned in the last section. 

/* gatsby-node.js */ const createIndex = async (blogNodes, type, cache) => {   const documents = []   // Iterate over all posts    for (const node of blogNodes) {     const html = await type.getFields().html.resolve(node)     // Once html is resolved, add a slug-title-content object to the documents array     documents.push({       slug: node.fields.slug,       title: node.frontmatter.title,       content: striptags(html),     })   }   const index = lunr(function() {     this.ref(`slug`)     this.field(`title`)     this.field(`content`)     for (const doc of documents) {       this.add(doc)     }   })   return index.toJSON() }

Have you noticed that instead of accessing the HTML element directly with  const html = node.html, we’re using an  await expression? That’s because node.html isn’t available yet. The gatsby-transformer-remark plugin (used by our starter to parse Markdown files) does not generate HTML from markdown immediately when creating the MarkdownRemark nodes. Instead,  html is generated lazily when the html field resolver is called in a query. The same actually applies to the excerpt that we will need in just a bit.

Let’s look ahead and think about how we are going to display search results. Users expect to obtain a link to the matching post, with its title as the anchor text. Very likely, they wouldn’t mind a short excerpt as well.

Lunr’s search returns an array of objects representing matching documents by the ref property (which is the unique document key slug in our example). This array does not contain the document title nor the content. Therefore, we need to store somewhere the post title and excerpt corresponding to each slug. We can do that within our LunrIndex as below:

/* gatsby-node.js */ const createIndex = async (blogNodes, type, cache) => {   const documents = []   const store = {}   for (const node of blogNodes) {     const {slug} = node.fields     const title = node.frontmatter.title     const [html, excerpt] = await Promise.all([       type.getFields().html.resolve(node),       type.getFields().excerpt.resolve(node, { pruneLength: 40 }),     ])     documents.push({       // unchanged     })     store[slug] = {       title,       excerpt,     }   }   const index = lunr(function() {     // unchanged   })   return { index: index.toJSON(), store } }

Our search index changes only if one of the posts is modified or a new post is added. We don’t need to rebuild the index each time we run gatsby develop. To avoid unnecessary builds, let’s take advantage of the cache API:

/* gatsby-node.js */ const createIndex = async (blogNodes, type, cache) => {   const cacheKey = `IndexLunr`   const cached = await cache.get(cacheKey)   if (cached) {     return cached   }   // unchanged   const json = { index: index.toJSON(), store }   await cache.set(cacheKey, json)   return json }

Enhancing pages with the search form component

We can now move on to the front end of our implementation. Let’s start by building a search form component.

touch src/components/search-form.js 

I opt for a straightforward solution: an input of type="search", coupled with a label and accompanied by a submit button, all wrapped within a form tag with the search landmark role.

We will add two event handlers, handleSubmit on form submit and handleChange on changes to the search input.

/* src/components/search-form.js */ import React, { useState, useRef } from "react" import { navigate } from "@reach/router" const SearchForm = ({ initialQuery = "" }) => {   // Create a piece of state, and initialize it to initialQuery   // query will hold the current value of the state,   // and setQuery will let us change it   const [query, setQuery] = useState(initialQuery)      // We need to get reference to the search input element   const inputEl = useRef(null)    // On input change use the current value of the input field (e.target.value)   // to update the state's query value   const handleChange = e => {     setQuery(e.target.value)   }      // When the form is submitted navigate to /search   // with a query q paramenter equal to the value within the input search   const handleSubmit = e => {     e.preventDefault()     // `inputEl.current` points to the mounted search input element     const q = inputEl.current.value     navigate(`/search?q=$ {q}`)   }   return (     <form role="search" onSubmit={handleSubmit}>       <label htmlFor="search-input" style={{ display: "block" }}>         Search for:       </label>       <input         ref={inputEl}         id="search-input"         type="search"         value={query}         placeholder="e.g. duck"         onChange={handleChange}       />       <button type="submit">Go</button>     </form>   ) } export default SearchForm

Have you noticed that we’re importing navigate from the @reach/router package? That is necessary since neither Gatsby’s <Link/> nor navigate provide in-route navigation with a query parameter. Instead, we can import @reach/router — there’s no need to install it since Gatsby already includes it — and use its navigate function.

Now that we’ve built our component, let’s add it to our home page (as below) and 404 page.

/* src/pages/index.js */ // unchanged import SearchForm from "../components/search-form" const BlogIndex = ({ data, location }) => {   // unchanged   return (     <Layout location={location} title={siteTitle}>       <SEO title="All posts" />       <Bio />       <SearchForm />       // unchanged

Search results page

Our SearchForm component navigates to the /search route when the form is submitted, but for the moment, there is nothing behing this URL. That means we need to add a new page:

touch src/pages/search.js 

I proceeded by copying and adapting the content of the the index.js page. One of the essential modifications concerns the page query (see the very bottom of the file). We will replace allMarkdownRemark with the LunrIndex field. 

/* src/pages/search.js */ import React from "react" import { Link, graphql } from "gatsby" import { Index } from "lunr" import Layout from "../components/layout" import SEO from "../components/seo" import SearchForm from "../components/search-form" 
 // We can access the results of the page GraphQL query via the data props const SearchPage = ({ data, location }) => {   const siteTitle = data.site.siteMetadata.title      // We can read what follows the ?q= here   // URLSearchParams provides a native way to get URL params   // location.search.slice(1) gets rid of the "?"    const params = new URLSearchParams(location.search.slice(1))   const q = params.get("q") || "" 
   // LunrIndex is available via page query   const { store } = data.LunrIndex   // Lunr in action here   const index = Index.load(data.LunrIndex.index)   let results = []   try {     // Search is a lunr method     results = index.search(q).map(({ ref }) => {       // Map search results to an array of {slug, title, excerpt} objects       return {         slug: ref,         ...store[ref],       }     })   } catch (error) {     console.log(error)   }   return (     // We will take care of this part in a moment   ) } export default SearchPage export const pageQuery = graphql`   query {     site {       siteMetadata {         title       }     }     LunrIndex   } `

Now that we know how to retrieve the query value and the matching posts, let’s display the content of the page. Notice that on the search page we pass the query value to the <SearchForm /> component via the initialQuery props. When the user arrives to the search results page, their search query should remain in the input field. 

return (   <Layout location={location} title={siteTitle}>     <SEO title="Search results" />     {q ? <h1>Search results</h1> : <h1>What are you looking for?</h1>}     <SearchForm initialQuery={q} />     {results.length ? (       results.map(result => {         return (           <article key={result.slug}>             <h2>               <Link to={result.slug}>                 {result.title || result.slug}               </Link>             </h2>             <p>{result.excerpt}</p>           </article>         )       })     ) : (       <p>Nothing found.</p>     )}   </Layout> )

You can find the complete code in this gatsby-starter-blog fork and the live demo deployed on Netlify.

Instant search widget

Finding the most “logical” and user-friendly way of implementing search may be a challenge in and of itself. Let’s now switch to the real-life example of tartanify.com — a Gatsby-powered website gathering 5,000+ tartan patterns. Since tartans are often associated with clans or organizations, the possibility to search a tartan by name seems to make sense. 

We built tartanify.com as a side project where we feel absolutely free to experiment with things. We didn’t want a classic search results page but an instant search “widget.” Often, a given search keyword corresponds with a number of results — for example, “Ramsay” comes in six variations.  We imagined the search widget would be persistent, meaning it should stay in place when a user navigates from one matching tartan to another.

Let me show you how we made it work with Lunr.  The first step of building the index is very similar to the gatsby-starter-blog example, only simpler:

/* gatsby-node.js */ exports.createResolvers = ({ cache, createResolvers }) => {   createResolvers({     Query: {       LunrIndex: {         type: GraphQLJSONObject,         resolve(source, args, context) {           const siteNodes = context.nodeModel.getAllNodes({             type: `TartansCsv`,           })           return createIndex(siteNodes, cache)         },       },     },   }) } const createIndex = async (nodes, cache) => {   const cacheKey = `LunrIndex`   const cached = await cache.get(cacheKey)   if (cached) {     return cached   }   const store = {}   const index = lunr(function() {     this.ref(`slug`)     this.field(`title`)     for (node of nodes) {       const { slug } = node.fields       const doc = {         slug,         title: node.fields.Unique_Name,       }       store[slug] = {         title: doc.title,       }       this.add(doc)     }   })   const json = { index: index.toJSON(), store }   cache.set(cacheKey, json)   return json }

We opted for instant search, which means that search is triggered by any change in the search input instead of a form submission.

/* src/components/searchwidget.js */ import React, { useState } from "react" import lunr, { Index } from "lunr" import { graphql, useStaticQuery } from "gatsby" import SearchResults from "./searchresults" 
 const SearchWidget = () => {   const [value, setValue] = useState("")   // results is now a state variable    const [results, setResults] = useState([]) 
   // Since it's not a page component, useStaticQuery for quering data   // https://www.gatsbyjs.org/docs/use-static-query/   const { LunrIndex } = useStaticQuery(graphql`     query {       LunrIndex     }   `)   const index = Index.load(LunrIndex.index)   const { store } = LunrIndex   const handleChange = e => {     const query = e.target.value     setValue(query)     try {       const search = index.search(query).map(({ ref }) => {         return {           slug: ref,           ...store[ref],         }       })       setResults(search)     } catch (error) {       console.log(error)     }   }   return (     <div className="search-wrapper">       // You can use a form tag as well, as long as we prevent the default submit behavior       <div role="search">         <label htmlFor="search-input" className="visually-hidden">           Search Tartans by Name         </label>         <input           id="search-input"           type="search"           value={value}           onChange={handleChange}           placeholder="Search Tartans by Name"         />       </div>       <SearchResults results={results} />     </div>   ) } export default SearchWidget

The SearchResults are structured like this:

/* src/components/searchresults.js */ import React from "react" import { Link } from "gatsby" const SearchResults = ({ results }) => (   <div>     {results.length ? (       <>         <h2>{results.length} tartan(s) matched your query</h2>         <ul>           {results.map(result => (             <li key={result.slug}>               <Link to={`/tartan/$ {result.slug}`}>{result.title}</Link>             </li>           ))}         </ul>       </>     ) : (       <p>Sorry, no matches found.</p>     )}   </div> ) export default SearchResults

Making it persistent

Where should we use this component? We could add it to the Layout component. The problem is that our search form will get unmounted on page changes that way. If a user wants to browser all tartans associated with the “Ramsay” clan, they will have to retype their query several times. That’s not ideal.

Thomas Weibenfalk has written a great article on keeping state between pages with local state in Gatsby.js. We will use the same technique, where the wrapPageElement browser API sets persistent UI elements around pages. 

Let’s add the following code to the gatsby-browser.js. You might need to add this file to the root of your project.

/* gatsby-browser.js */ import React from "react" import SearchWrapper from "./src/components/searchwrapper" export const wrapPageElement = ({ element, props }) => (   <SearchWrapper {...props}>{element}</SearchWrapper> )

Now let’s add a new component file:

touch src/components/searchwrapper.js

Instead of adding SearchWidget component to the Layout, we will add it to the SearchWrapper and the magic happens. ✨

/* src/components/searchwrapper.js */ import React from "react" import SearchWidget from "./searchwidget" 
 const SearchWrapper = ({ children }) => (   <>     {children}     <SearchWidget />   </> ) export default SearchWrapper

Creating a custom search query

At this point, I started to try different keywords but very quickly realized that Lunr’s default search query might not be the best solution when used for instant search.

Why? Imagine that we are looking for tartans associated with the name MacCallum. While typing “MacCallum” letter-by-letter, this is the evolution of the results:

  • m – 2 matches (Lyon, Jeffrey M, Lyon, Jeffrey M (Hunting))
  • ma – no matches
  • mac – 1 match (Brighton Mac Dermotte)
  • macc – no matches
  • macca – no matches
  • maccal – 1 match (MacCall)
  • maccall – 1 match (MacCall)
  • maccallu – no matches
  • maccallum – 3 matches (MacCallum, MacCallum #2, MacCallum of Berwick)

Users will probably type the full name and hit the button if we make a button available. But with instant search, a user is likely to abandon early because they may expect that the results can only narrow down letters are added to the keyword query.

 That’s not the only problem. Here’s what we get with “Callum”:

  • c – 3 unrelated matches
  • ca – no matches
  • cal – no matches
  • call – no matches
  • callu – no matches
  • callum – one match 

You can see the trouble if someone gives up halfway into typing the full query.

Fortunately, Lunr supports more complex queries, including fuzzy matches, wildcards and boolean logic (e.g. AND, OR, NOT) for multiple terms. All of these are available either via a special query syntax, for example: 

index.search("+*callum mac*")

We could also reach for the index query method to handle it programatically.

The first solution is not satisfying since it requires more effort from the user. I used the index.query method instead:

/* src/components/searchwidget.js */ const search = index   .query(function(q) {     // full term matching     q.term(el)     // OR (default)     // trailing or leading wildcard     q.term(el, {       wildcard:         lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING,     })   })   .map(({ ref }) => {     return {       slug: ref,       ...store[ref],     }   })

Why use full term matching with wildcard matching? That’s necessary for all keywords that “benefit” from the stemming process. For example, the stem of “different” is “differ.”  As a consequence, queries with wildcards — such as differe*, differen* or  different* — all result in no matches, while the full term queries differe, differen and different return matches.

Fuzzy matches can be used as well. In our case, they are allowed uniquely for terms of five or more characters:

q.term(el, { editDistance: el.length > 5 ? 1 : 0 }) q.term(el, {   wildcard:     lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING, })

The handleChange function also “cleans up” user inputs and ignores single-character terms:

/* src/components/searchwidget.js */   const handleChange = e => {   const query = e.target.value || ""   setValue(query)   if (!query.length) {     setResults([])   }   const keywords = query     .trim() // remove trailing and leading spaces     .replace(/*/g, "") // remove user's wildcards     .toLowerCase()     .split(/s+/) // split by whitespaces   // do nothing if the last typed keyword is shorter than 2   if (keywords[keywords.length - 1].length < 2) {     return   }   try {     const search = index       .query(function(q) {         keywords           // filter out keywords shorter than 2           .filter(el => el.length > 1)           // loop over keywords           .forEach(el => {             q.term(el, { editDistance: el.length > 5 ? 1 : 0 })             q.term(el, {               wildcard:                 lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING,             })           })       })       .map(({ ref }) => {         return {           slug: ref,           ...store[ref],         }       })     setResults(search)   } catch (error) {     console.log(error)   } }

Let’s check it in action:

  • m – pending
  • ma – 861 matches
  • mac – 600 matches
  • macc – 35 matches
  • macca – 12 matches
  • maccal – 9 matches
  • maccall – 9 matches
  • maccallu – 3 matches
  • maccallum – 3 matches

Searching for “Callum” works as well, resulting in four matches: Callum, MacCallum, MacCallum #2, and MacCallum of Berwick.

There is one more problem, though: multi-terms queries. Say, you’re looking for “Loch Ness.” There are two tartans associated with  that term, but with the default OR logic, you get a grand total of 96 results. (There are plenty of other lakes in Scotland.)

I wound up deciding that an AND search would work better for this project. Unfortunately, Lunr does not support nested queries, and what we actually need is (keyword1 OR *keyword*) AND (keyword2 OR *keyword2*). 

To overcome this, I ended up moving the terms loop outside the query method and intersecting the results per term. (By intersecting, I mean finding all slugs that appear in all of the per-single-keyword results.)

/* src/components/searchwidget.js */ try {   // andSearch stores the intersection of all per-term results   let andSearch = []   keywords     .filter(el => el.length > 1)     // loop over keywords     .forEach((el, i) => {       // per-single-keyword results       const keywordSearch = index         .query(function(q) {           q.term(el, { editDistance: el.length > 5 ? 1 : 0 })           q.term(el, {             wildcard:               lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING,           })         })         .map(({ ref }) => {           return {             slug: ref,             ...store[ref],           }         })       // intersect current keywordSearch with andSearch       andSearch =         i > 0           ? andSearch.filter(x => keywordSearch.some(el => el.slug === x.slug))           : keywordSearch     })   setResults(andSearch) } catch (error) {   console.log(error) }

The source code for tartanify.com is published on GitHub. You can see the complete implementation of the Lunr search there.

Final thoughts

Search is often a non-negotiable feature for finding content on a site. How important the search functionality actually is may vary from one project to another. Nevertheless, there is no reason to abandon it under the pretext that it does not tally with the static character of Jamstack websites. There are many possibilities. We’ve just discussed one of them.

And, paradoxically in this specific example, the result was a better all-around user experience, thanks to the fact that implementing search was not an obvious task but instead required a lot of deliberation. We may not have been able to say the same with an over-the-counter solution.

The post How to Add Lunr Search to your Gatsby Website appeared first on CSS-Tricks.


, , ,

Jetpack Instant Search!

Jetpack has had a search feature for a while. Flip it on, and it replaces your built-in WordPress search (which is functional, but not particularly good) with an Elasticsearch-powered solution that is faster and has better results. I’ve been using that for quite a while here on CSS-Tricks, which was an upgrade from using a Google Custom Search Engine.

Jetpack just upped their game again with a brand new search upgrade. You can use Jetpack search how you were already, or you can flip on Instant Search and take advantage of this all-new search experience (inside and out).

Flipping on Jetpack Instant Search from Jetpack Settings

A Full Page Experience

Instant Search provides a full page-covering search experience. I think it’s awesome. When a user is searching, that’s the mindset they are in and giving them all the space they need to accomplish that goal is great. Here’s me searching (video):

Note that I misspell the word “margin” and the results are fine.
You get a full page experience on mobile as well.

Best I can tell, CSS-Tricks gets a couple hundred thousand on-site searches per month, so having a great experience there is very important to me. I don’t even wanna mess around with bad on-site search experiences, or products that are too expensive. I’d rather send people to a site-scoped Google search than a bad on-site search. Fortunately, Instant Search is about as good of an on-site search experience as I can imagine, especially for the zero-work it takes to implement.

Design Control

You have some control over the look of things from the Customizer.

Instant Search is designed to work on any site, so you probably don’t need to do much. I was really surprised how well it worked out-of-the-box for CSS-Tricks. As a CSS control freak, I did ship a handful of design tweaks to it, but that’s just because I love doing that kind of thing.

Tweaks No Longer Needed

With the previous version of Jetpack Search, I had custom code in place to tweak Elasticsearch. I did things like factoring in comment counts as an indicator of popularity, so that I could be sure our best content was high in the results. Remember as powerful as this search is, it doesn’t have a model of the entire internet to calculate relevancy from like Google does. Good news though:

To further improve our search algorithm, we started experimenting with adding the percentage of pageviews from the past 30 days into the index. We ended up finding that pageviews are a much better ranking signal because it somewhat combines both popularity and recency. So now most of our result ranking is strongly influenced by the number of pageviews a post or page gets. Conveniently, if you get a lot of Google Search traffic, our search results should be heavily influenced by Google’s ranking algorithm.

Emphasis mine. With Jetpack Instant Search, I was able to rip all that custom code out (removing code always feels great) because the new algorithms are doing a great job with ranking results.


Now Jetpack Search is ala-carte rather than baked into specific plans. Don’t need it, you don’t pay for it? Only need this feature? You can buy it regardless of what plan you are on.

I’m told the pricing is about scope. Jetpack plans are about features, not scale of site, but that doesn’t make much sense for search where the scale of the site matters a ton. So it’s a sliding scale based on the “records” you have, which are basically posts and pages.

Sliding scale of pricing

I would think a lot of sites fall into the $ 25/month (15% off for annual) category. You probably mostly start caring about on-site search above 1,000 records and 10,000 records is a ton. I pay for the tier one up from that (~$ 612 a year) only because our (now archived) bbPress forums pushes the number over 10,000. That’s a perfectly fair price for this for a site like mine.

Wish List

My #1 thing is that I wish it was easy to remove certain things from search results. We have tons and tons of records from our bbPress forums that I made the (hard) call to close this year. Removing those records would pull me down into a smaller pricing tier, but more importantly, I’d rather just not show those results in search at all.

It’s not just CSS-Tricks being in an unusual situation. I’ve also turned on Jetpack Instant Search on the CodePen Documentation.

In that circumstance, I’d consider turning removing blog posts (believe it or not) from the search results, so instead just the Pages would show up which are our core documentation there. Or perhaps even better, blog posts are just turned off as a filter by default, but users could flip them on to see them in the results.

All in all, this is a huge upgrade to Jetpack and yet another reason I consider it the most important plugin I run on my WordPress sites. If you’re curious about other Jetpack features we use, we made a special page for that.

The post Jetpack Instant Search! appeared first on CSS-Tricks.


, ,

Product Search and Filters Are a Snap With WooCommerce

Let’s say you visit an e-commerce site because you want to buy the latest banana peeler model. Bananas are hard enough to peel, right? Only a tool will do!

What’s the first thing you’re going to do on the site? Chances are, it’s entering something into the (hopefully) prominent search field. You need a way to tell the store what you’re looking for in order to get recommendations and that’s the first step.

What’s next? Again, chances are you will need to get more specific with what you’re looking for. You may need the search results to be filtered to get a more precise and increase the chance that your banana peeler surfaces to the top. That might be filtering by a specific brand, color, size, availability, or whatever.

Developing an e-commerce site is already a difficult task and throwing in things like search and filters make it even tougher. That’s why WooCommerce is worth looking into. It’s a WordPress plugin that transforms any site into a full-fledged e-commerce machine, complete with product inventory, shipping methods, payment gateways and, yes, front-end functionality for things like search and filtering.

Take the latest WooCommerce release for example. It introduces a new “All Products” block for the Gutenburg block editor. You can literally drop this block on any WordPress page and it will render a fully customizable grid of products from your inventory. Pretty slick!

Photo courtesy of WooCommerce

And what about filtering? There are specific blocks for that too. Drop any number of filtering options on the same page as the All Products block and the filters will automatically know to filter from the products in the block. And there’s a ton of ways to filter products, including price and attributes (e.g. color, size, etc.). Oh, and filters can be combined, of course, so they can be used together for even more fine-grained search results.

Photo courtesy of WooCommerce

This is just one of the many, many ways WooCommerce is lowering the bar to entry for e-commerce while setting high standards for what to expect on an e-commerce site. It’s free and has been downloaded more than 40 million times… now it’s your turn to give it a spin.

If you’re thinking of pulling the trigger on WooCommerce and think you might need to buy some plugins, it’s about to the absolute perfect time of year to do that. Kicking off on Black Friday and running through Cyber Monday, literally everything WooCommerce is 40% off. And if you’d rather do eCommerce on a totally hosted platform, WordPress.com can do that and that’s all 20% off.

The post Product Search and Filters Are a Snap With WooCommerce appeared first on CSS-Tricks.


, , , ,

Weekly Platform News: Internet Explorer Mode, Speed Report in Search Console, Restricting Notification Prompts

In this week’s roundup: Internet Explorer finds its way into Edge, Google Search Console touts a new speed report, and Firefox gives Facebook’s notification the silent treatment.

Let’s get into the news!

Edge browser with new Internet Explorer mode launches in January

Microsoft expects to release the new Chromium-based Edge browser on January 15, on both Windows and macOS. This browser includes a new Internet Explorer mode that allows Edge to automatically and seamlessly render tabs containing specific legacy content (e.g., a company’s intranet) using Internet Explorer’s engine instead of Edge’s standard engine (Blink).

Here’s a sped-up excerpt from Fred Pullen’s presentation that shows the new Internet Explorer mode in action:

(via Kyle Pflug)

Speed report experimentally available in Google Search Console

The new Speed report in Google’s Search Console shows how your website performs for real-world Chrome users (both on mobile and desktop). Pages that “pass a certain threshold of visits” are categorized into fast, moderate, and slow pages.

Tip: After fixing a speed issue, use the “Validate fix” button to notify Google Search. Google will verify the fix and re-index the pages if the issue is resolved.

(via Google Webmasters)

Facebook’s notification prompt will disappear in Firefox

Firefox will soon start blocking notification prompts on websites that request the notification permission immediately on page load (Facebook does this). Instead of the prompt, a small “speech balloon” icon will be shown in the URL bar.

Websites will still be able to show a notification prompt in Firefox as long as they request permission in response to a user interaction (a click, tap, or key press).

(via Marcos Càceres)

More news…

Read more news in my weekly newsletter for web developers. Pledge as little as $ 2 per month to get the latest news from me via email every Monday.

More News →

The post Weekly Platform News: Internet Explorer Mode, Speed Report in Search Console, Restricting Notification Prompts appeared first on CSS-Tricks.


, , , , , , , , , , , ,

Show Search Button when Search Field is Non-Empty

I think the :placeholder-shown selector is tremendously cool. It allows you to select the placeholder of an input (<input placeholder="...">) when that placeholder is present. Meaning, the input does not yet have any value. You might think input[value] could do that, or help match on the actual value, but it can’t.

This makes :placeholder-shown one of the few CSS properties that we have that can react to user-initiated state joining the likes of :hover-and-friends, :checked (checkbox hack!), and the also-awesome :focus-within.

One way we can use it is to check whether the user entered any text into a search field. If yes, then reveal the search button visually (never hide it for assistive tech). If no, then leave it hidden. It’s just a fun little “space-saving” technique not terrible unlike float labels.

So, perhaps we start with a semantic search form:

<form action="#" method="GET" class="search-form">   <!-- Placeholders aren't labels! So let's have a real label -->   <label for="search" class="screen-reader-text">Search</label>   <input id="search" type="search" class="search-input" placeholder="Enter search terms...">   <button class="search-button">Search</button> </form>

We hide the search label visually with one of those special screen-reader-only classes because it will always be hidden visually. But we’ll hide the button by pushing it outside the form with hidden overflow.

.search-form {   /* we'll re-use this number, so DRYing up */   --searchButtonWidth: 75px;    overflow: hidden;   position: relative; }  .search-input {   /* take full width of form */   width: 100%; }  .search-button {   position: absolute;    /* push outside the form, hiding it */   left: 100%;   width: var(--searchButtonWidth); }

Then the trick is to pull the search button back in when the placeholder goes away (user has entered a value):

/* ... */  .search-input:not(:placeholder-shown) ~ .search-button {   /* pull back the negative value of the width */   transform: translateX(calc(-1 * var(--searchButtonWidth))); } .search-button {   position: absolute;    left: 100%;   width: var(--searchButtonWidth);   /* animate it */   transition: 0.2s; }

Which ends up like this:

See the Pen
:placeholder-shown revealing button
by Chris Coyier (@chriscoyier)
on CodePen.

I saw this idea in a Pen by Liam Johnston. Cool idea, Liam!

I know that using the placeholder attribute at all is questionable, so your mileage may vary. I’ll admit that I’m mostly intrigued by the state-handling aspects of it directly in CSS and how it can be used — like the infamous checkbox hack.

Support is good. One of those where when Edge is firmly Chromium, it’s in the clear.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Opera Firefox IE Edge Safari
47 34 51 11* 76 9

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
9.0-9.2 No No 76 78 68

The post Show Search Button when Search Field is Non-Empty appeared first on CSS-Tricks.


, , , ,

Web Developer Search History

Sophie Koonin blogged “Everything I googled in a week as a professional software engineer,” which was a fascinating look into the mind of a web developer and what they need to look up during day-to-day work. We all joke that we just Google stuff (or StackOverflow stuff) we don’t know off the bat and copy and paste, but this is more to the heart of it.

A few just from one day:

react-apollo release notes
jest silence warnings – don’t judge me, ok?
semantic HTML contact details – wanted to check if the <address> tag was relevant here
dominos accessibility – popcorn.gif

Gift Egwuenu followed up with “The Art of Googling.”

A few:

  • Filter an Array with JavaScript – I was working on a chore and needed to see how the filter method works
  • Take a screenshot on Mac
  • Center a div with Grid
  • Undo a git commit
  • Grid with React Native – checking if this was a thing with React Native

Reading these, I alternate from “eeeeesh, good luck” to “I wish I could have been there because I could have probably helped.”

This is a fun little blogging trend I’d love to see more people do. (Reminds me of that “How we do CSS at [company]” thing.)

I’ll do my last few days, unearthed from Google’s My Activity area, filtered to search-only.

This was just a couple of hours yesterday morning, with some noise and distracting stuff removed:

  • Sara Vieira – Was looking for her Twitter to credit a tweet.
  • javascript closest – Was looking for the DOM version rather than the jQuery version
  • jquery parent – Confirming that was a real API it had
  • minimap moz element – Finding an article that was a great use case for the
  • element() function in CSS
  • web unleashed – Going to this conference later in the week and needed to look at the site
  • server log analytics
  • states in australia
  • view html source in spark mail
  • bat crypto
  • html validator
  • html unescape
  • should email html have title?
  • window.scrollto
  • useCallback
  • rails rss feed content type
  • content type for rss feeds
  • river city girls

You next! You next!

The post Web Developer Search History appeared first on CSS-Tricks.


, ,