Navigate back to the homepage

Search on Static Sites | Building The Varsity’s Student Handbook part 1

Rahul Tarak
September 19th, 2020 · 3 min read

This is going to be a series of three or four posts talking about the more interesting things we did while making handbook.thevarsity.ca. If you haven’t checked it out yet, I would highly recommend it; for starters, this series will make a lot more sense but also it is just full of really good articles and illustrations.

For some context, this year as a result of COVID-19 the annual Varsity student handbook had to be shifted from a physical version to a digital medium. As the first digital handbook we definitely had some pain points but also a lot of opportunities to innovate on what had been done before.

At The Varsity, we use WordPress and Elementor and I am personally not a fan, and definitely planning to move away from them more and more through the coming year. But moving a huge site like thevarsity.ca with over 20 years of articles is very complicated; the handbook on the other hand gave us an opportunity for a fresh start.

At The Varsity Volume 141’s engineering team, we ♥ TS and are always looking to modernize our tech stack. For the handbook, we went with a very similar stack to Open Varsity with:

  • Gatsby
  • React
  • TS
  • Netlify CMS
  • Bulma

You can start with same netlify starter template as us, here:

1git clone https://github.com/matsuri-tech/gatsby-starter-netlify-cms

This is a typescript version of the netlify starter template.

I am not really planning to talk about the basic setup or creating basic articles but in this post rather I want to focus on a few main topics I’ve listed below.

  • Search on a static site
  • Custom React TS hooks
  • Animating lines on the frontpage
  • Specific image optimization with Gatsby Image

Search on a static site

I thought this would be a much more complicated problem then it really was. I had heard of things like Angolia and such, but I expected setting up search to be a tedious process. It really wasn’t and I want to talk about how I did it.

We used elasticlunr search, which you can find more about here.

To install the package, use this.

1yarn install @gatsby-contrib/gatsby-plugin-elasticlunr-search
2# or
3npm install --save @gatsby-contrib/gatsby-plugin-elasticlunr-search

Setting up Gatsby Config

Here you will notice a slight difference from the documentation, where we pass the slug as the path parameter. This is a required change if you want to open the link after search rather than just display titles.

1{
2 resolve: `@gatsby-contrib/gatsby-plugin-elasticlunr-search`,
3 options: {
4 // Fields to index
5 fields: [`title`, `tags`],
6 // How to resolve each field`s value for a supported node type
7 resolvers: {
8 // For any node of type MarkdownRemark, list how to resolve the fields` values
9 MarkdownRemark: {
10 title: node => node.frontmatter.title,
11 tags: node => node.frontmatter.tags,
12 path: node => node.fields.slug
13 }
14 }
15 }
16 }

In this same vein, you could easily add more fields to index or pass more parameters to the markdown remark.

Setting up the search wrapper

This would be the most basic setup for the search wrapper, but you would normally expect to find this on a navbar or something like that. Then obviously it would have more styling and be more complex. Our search wrapper is our navbar, which you can find here

1const SearchWrapper = () => {
2 <StaticQuery
3 query={graphql`
4 query SearchIndexQuery {
5 siteSearchIndex {
6 index
7 }
8 }
9 `}
10 render={(data) => (
11 <header>
12 <Search searchIndex={data.siteSearchIndex.index} />
13 </header>
14 )}
15 />;
16};

Note: to the best of my knowledge elasticlunr does not have typescript type definitions and hence you won’t get proper type support on it.

Here I am going to directly show you our search page, as it has the linking and everything built in. Feel free to restyle it however you want. This project was using styled-jsx but there is no need for this.

Similarly, we can also ignore the isMobile and toggle as they are meant to change the styling of the element depending on state changes.

1import { Index } from "elasticlunr";
2import { Link } from "gatsby";
3import React, { ChangeEvent, useEffect, useState } from "react";
4
5import { isMobile } from "react-device-detect";
6
7type SearchProps = { searchIndex: any; toggle: boolean };
8
9// Search component
10const Search: React.FC<SearchProps> = ({ searchIndex, toggle }) => {
11 const [state, setState] = useState<{
12 query: string;
13 results: Array<{ path: string; title: string; id: number }>;
14 }>({
15 query: "",
16 results: [],
17 });
18 const [index, setIndex] = useState();
19
20 const getOrCreateIndex = () => {
21 if (!index) {
22 setIndex(Index.load(searchIndex));
23 }
24 };
25
26 const search = (evt: ChangeEvent<HTMLInputElement>) => {
27 const query = evt.target.value;
28 getOrCreateIndex();
29 if (index) {
30 setState({
31 query,
32 // Query the index with search string to get an [] of IDs
33 results: index
34 .search(query, { expand: true })
35 // Map over each ID and return the full document
36 .map(({ ref }: { ref: any }) => index.documentStore.getDoc(ref)),
37 });
38 }
39 };
40
41 useEffect(() => {
42 if (!toggle) {
43 setState({ query: ``, results: [] });
44 }
45 console.log(toggle);
46 }, [toggle]);
47
48 return (
49 <div>
50 <input
51 type="text"
52 value={state.query}
53 onChange={search}
54 className="input is-hovered is-large"
55 placeholder="Search ..."
56 />
57 {state.results.length && toggle ? (
58 <ul
59 className="py-3 mt-2"
60 style={{
61 position: isMobile ? "static" : "absolute",
62 width: isMobile ? "95vw" : "23vw",
63 borderRadius: "5px",
64 backgroundColor: "rgba(51, 51, 51, 0.8)",
65 display: "flex",
66 flexDirection: "column",
67 justifyContent: "center",
68 }}
69 >
70 {state.results.map((page, index) => (
71 <li key={page.id}>
72 <Link to={`${page.path}`} className="has-text-white">
73 <p className="search-result">{page.title}</p>
74 </Link>
75 <div className="divider-wrapper">
76 {index !== state.results.length - 1 ? (
77 <div className="divider" />
78 ) : null}
79 </div>
80 {/* @ts-ignore Styled JSX*/}
81 <style jsx>
82 {`
83 .search-result {
84 word-wrap: auto;
85 margin: 0 5px;
86 }
87 .divider-wrapper {
88 display: flex;
89 justify-content: center;
90 }
91 .divider {
92 display: block;
93 position: relative;
94 border-top: 0.1rem solid white;
95 height: 0.1rem;
96 margin: 0.5rem 0;
97 text-align: center;
98 width: 90%;
99 }
100 `}
101 </style>
102 </li>
103 ))}
104 </ul>
105 ) : null}
106 </div>
107 );
108};
109export default Search;

And that is about it — we now have a search set up on a static site.

Conclusion

Static sites are genuinely amazing; they provide so many features and a lot of flexibility to developers while having absolutely amazing performance for end-users.

While there are inherent drawbacks for certain use cases with using static sites, they are really good at what they do and more and more, we are seeing that they are able to have features you’d only expect in non-static sites, such as powerful searching.

In the next part of this series, I will likely be covering custom react hooks and showing off a Varsity package with typescript react hooks, which will be expanded upon through the year as we create more hooks for our own projects.

More articles from Varsity Publications Inc

Let’s talk about speed

How I took The Varsity from a failing 50 per cent to a 90 per cent score in two days; the story of why WordPress is not good at speed and how to fix that.

July 30th, 2020 · 6 min read

Solving A SSL Error with Artificial Intelligence | Adventures in Crashing The Varsity with Rahul Tarak Part 1

Solving SSL/DNS error using Artificial Intelligence | A series where I go through every incident of me crashing the site, how I managed to do it, what I learned from it, and what I changed

July 26th, 2020 · 6 min read
© 2020 Varsity Publications Inc
About Open VarsityVarsity Status
Link to $https://twitter.com/TheVarsityLink to $https://github.com/TheVarsityLink to $https://www.linkedin.com/company/varsity-publications-inc./Link to $https://thevarsity.ca/