Skip to content

AlexTechNoir/Next.js-Strapi-Blog

Repository files navigation

Next.js Blog with Strapi

Static demo blog. Deployed on Vercel.


WARNING!

Strapi is deployed on Heroku. Due to Heroku's financial decision to shut down free plans, this project will most likely crash starting with 28.11.22. But all code examples in this repo are valid! You can still study it and learn how I've done some features!

I tried some alternatives and with couple of failures, unfortunately, right now I don't have time to explore for more.

If you also switching from Heroku, be careful with Railway - if you create an account with one e-mail and then link a GitHub account with another (different) e-mail, they may ban you by mistake for "multiple accounts". Tech support's not answering on ban appeals, at least in my case


Features:

  • static
  • responsive
  • mobile first
  • built with Strapi Headless CMS
  • Strapi uses MongoDB Atlas as database
  • images stored on Cloudinary
  • Strapi deployed on Heroku
  • autocomplete search
  • search results with highlighted match (hardcoded)
  • sound
  • dark mode with autodetect system preference and saved user's choice in LocalStorage
  • hamburger menu for mobile layout
  • "Load More" pagination
  • SEO-friendly article list initially fetched on server-side (paginated - client-side)
  • progressive responsive images
  • categories
  • Disqus comment section

How did I do...

Search with highlighted results and cut text around match
  1. When user enters value, we grab its changes.
  2. Search happens onSubmit.
  3. It's important not to use Next.js's dynamic routes here, because blog won't be static if we have pages, depending on user's request (but if you're OK with hybrid/dynamic blog, then it's alright to use them (btw, maybe optional catch all routes will somehow work with static site too, but I haven't tested it)). Instead we go to search page with query passed through "?" sign, here.
  4. Search page will fetch Strapi's API and will render the filtered results based on query parameter.
  5. In search result we convert markup to html and html to text.
  6. We divide text into array based on value match and create new text as array with "mark" tag around match.
  7. Then in useEffect, initially and every time the search value gets changed, we look for "mark" tags in text and cut the text around the first match (or just write "no match" if there is no match). The variable names should be self-explanatory here.
  8. It's important to wrap the text inside "p" tag with, for example, "span" tag to avoid React NotFoundError, which occurs when we re-render page (here: search for the second time) after manipulating the DOM (highlighting). Explained here.
  9. Last important thing: when we search for the second time and if in search result list we got result that was in the previous list (but with different match this time) then it will reflect previous highlighted match, which is not what we want. To avoid this we must explicitly remove old results and load new when we search. We can do this by triggering re-fetch - this will cause "isValidating" parameter to change on every search, replacing previous results with skeleton load and then load new results with correct highlighted match.
"Load More" pagination
  1. Featured articles are fetched on server-side as usual for SEO.
  2. We fetch paginated data on client-side with useSWRInfinite.
  3. We use "size" and "setSize" parameters to change page index.
  4. But instead of page index Strapi's API has Start param pointing at index from which data should be fetched and Limit param.
  5. In getKey function "pageIndex" parameter is the "size" parameter. It always starts with 0.
  6. We set limit param to 1 (fetch 1 article), start param - to "pageIndex + 7" (because first 7 artciles are already fetched on server-side).
  7. On every time user clicks "Load More" button, we increase size param to amount of times we need to fetch 1 artcile in one click (here we need 4, which depends on desktop layout).
  8. We also have to set initialSize parameter to 0, because we don't need paginated data initially, only on demand.

What did I use to make this demo:

Notes: