Bloggens stack

Harald Vinje

Del:

30. mai 2022

8 min lesning

Kategorier:WebBackend

En kjapp gjennomgang av teknologiene bak nettsiden og litt om hvordan de kan brukes!

Next.js, Sanity, Tailwind, Vercel

Oppdatering: Kildekoden er oppdatert til Next.js v15, så koden og noen av prinsippene i denne posten er delvis utdaterte.

TLDR; Next.js med Typescript, TailwindCSS, Sanity.io og Vercel for hosting. Kildekoden finnes på GitHub.

I dette innlegget tar jeg for meg de viktigste teknologiene bak nettisden, i tillegg til å utdype litt om hvorfor akkurat disse ble valgt.

Next.js

Next.js er et moderne webrammeverk bygget på toppen av React og er etter min erfaring den enkleste måten å utvikle Web Apper raskt og effektivt. I tillegg til å være behagelig å jobbe med har Next en del features som gjør at det ofte er å foretrekke foran ren React. Disse skal jeg gå litt mer inn på nå.

Routing

Next gjør routing enkelt for utvikleren. Alle filer under "pages"-mappen blir automatisk til en "page" i webappen. Som eksempel kan du se "pages"-mappen i kildekoden til bloggen.

1pages
2├── 404.tsx
3├── about.tsx
4├── _app.tsx
5├── index.tsx
6└── post
7 └── [slug].tsx

I dette tilfellet får vi:

  • tekblogg.dev -> pages/index.tsx
  • tekblogg.dev/about -> about.tsx

Jeg likte aldri å deale med React Router, så at Next ordner routingen falt meg i god smak! Legg også merke til filen post/[slug].tsx. Denne filen representerer et generisk blogginnlegg, og "sluget" blir bestemt basert på tittelen på bloggposten. I prinsippet kan du kalle de generiske filene dine hva du vil så lenge filnavnet er inne i klammeparenteser, men vanligvis bruker de å representere en form for identifikator ([slug].tsx, [id].tsx, [name].tsx etc.).

Static Site Generation

Et av problemene med ren React (og flere andre JS-rammeverk) er at mye av innholdet er generert via JavaScript-kode som manipulerer DOMen etter det er hentet til web browseren hos brukeren. Dette betyr at siden bygges opp først etter den er sendt over nettet. I en del tilfeller er dette riktig tilnærming, men på mange nettsteder er informasjonen vi henter stort sett statisk. Å konstruere siden ved hjelp av JavaScript i browseren om igjen og om igjen er derfor unødvendig. Menneskene bak Next.js ønsket å løse dette problemet på en utviklervennlig måte ved hjelp av funksjonen getStaticProps(context). I Next.js er getStaticProps et reservert navn på en funksjon som sørger for å konstruere opp HTMLen til den spesifikke "pagen" på nettsiden på forhånd. Det er dette som kalles "Static Site Generation" (SSG). Det er to store fordeler ved å bruke SSG:

  • Siden er ferdig lastet når du som bruker henter den fra webserveren. Det vil derfor gå kortere tid før alt innholdet er klart for brukeren.
  • Siden blir søkermotorvennlig! De fleste søkermotorer scanner gjennom HTML-dokumenter for å finne informasjon. Om informasjonen på siden din kun blir tilgjengelig etter JavaScript er lastet vil den ofte ignoreres av søkermotorer. Se bildet under!

Web crawling illustrasjon

Nå er det jo også mange sider hvor man ikke trenger å bli oppdaget av søkemotorer (f. eks en profilside med personlig data), eller hvor det meste av innholdet er dynamisk. På disse sidene burde du styre unne getStaticProps-funksjonen. Next har også funksjonen getServerSideProps(context), som på tilsvarende måte som getStaticProps bygger opp siden på serveren, men da under request time, altså når brukerern etterspør siden, og ikke på forhånd når siden bygges (build time). getServerSideProps er nyttig når du trenger fersk data som må være oppdatert i det brukeren spør etter det, og er spesielt nyttig om du trenger å gjøre litt tyngre beregninger som egner seg bedre på en server enn i browseren.

Ditt neste Next-prosjekt (next Next project 🤓) kan startes vha en av kommandoene:

1npx create-next-app@latest --typescript
2# or
3yarn create next-app --typescript
4# or
5pnpm create next-app -- --typescript

Typescriptflagget er selvfølgelig valgfritt, men sterkt anbefalt på alle nye prosjekter spør du meg.

TailwindCSS

Jeg har aldri følt meg spesielt dyktig på CSS, og har heller aldri hatt særlig interesse for det, men med TailwindCSS har jeg endelig funnet litt glede i det! Tailwind er en form for inline styling av HTML, som vil si at stylingen spesifiseres direkte i koden til HTML-elementene. Tailwind har definert et stort sett med hjelpeklasser for alt du måtte trenge av styling, som display, margin, padding, height, width, farger, font, text, animasjoner, conditional styling (som når du holder musen over et element f. eks), og mye mer. Med VSCodes plugin til Tailwind, som har autocomplete og god beskrivelse av hjelpefunksjonene, er det lett å se hva man har til disposisjon, noe som gjør styling lett og effektivt. Under ser du et eksmpel på en komponent fra bloggen som benytter seg rikelig av Tailwinds hjelpefunksjoner:

1 <span
2 className={`mr-2 mb-2 inline-block rounded-full bg-gray-200
3 px-3 py-1 text-sm font-semibold text-gray-700
4 ${clickable ? 'hover:cursor-pointer' : ''}
5 ${clicked ? 'bg-gray-400' : ''} `}
6 onClick={() => {
7 if (clickable && onCategoryClick) {
8 onCategoryClick(value)
9 setClicked(!clicked)
10 }
11 }}
12 >
13 {value}
14 </span>

Sanity.io

For å kunne skrive innlegg på bloggen på en brukervennlig måte trengte jeg et Content Management System (CMS). Jeg hadde blitt anbefalt Sanity.io som et godt alternativ tidligere, og da jeg i tillegg kom over en flott tutorial om hvordan man kan kombinere Sanity med en bloggside skrevet i Next.js ble valget enkelt. Med Sanity initialiserer du et prosjekt fra kommandolinja gjennom Sanity CLI, og så velger et template som passer prosjektet ditt. Du får da kildekoden til Sanity Studio lokalt, som er en Web App hvor du kan spesifisere datastrukturen til innholdet på nettsiden. Når du er fornøyd deployer du studioet, slik at det kan brukes av alle som lager innhold til siden din.

For å hente dataen fra Sanity til din egen Web App har du flere alternativer, men jeg valgte å gå for groq (som var nytt for meg, men ganske intuitivt, og dessuten det tutorialen anbefalte!). Når dette er på plass er det bare å sette i gang med skrivingen av blogginnlegg i Sanity Studio.

Resultatet

For å få en smakebit på teknologiene nevnt har jeg valgt ut pages/post/[slug].tsx-filen, som kan ses under. Litt nede i filen finner du funksjonene getStaticPaths og getStaticProps. getStaticPaths har jeg ikke nevnt ennå, men denne funksjonen er nødvendig for å hente "sluget" på alle bloggpostene som skal genereres. Når disse er hentet begynner genereringen av hvert enkelt blogginnlegg via getStaticProps. Legg merke til "revalidate: 60" i return-objektet til getStaticProps. Denne linjen forteller Next.js at selv om siden er statisk skal den revalideres og potensielt bygges på nytt én gang i minuttet for å sørge for oppdatert innhold fra bloggen. Dette er en ny feature i Next, og kalles Incremental Static Regeneration (ISR).

I både getStaticPaths og getStaticProps brukes groq for å hente data fra Sanity. I tillegg kan du se TailwindCSS hyppig brukt i mye av markupen.

[slug].tsx
1import { useTheme } from 'next-themes'
2import groq from 'groq'
3import { PortableTextBlock } from '@portabletext/types'
4import client from 'src/lib/sanityClient'
5import { formatAuthors, formatDate, richToPlainText } from 'src/lib/utils'
6import { RichText, urlFor } from 'src/components/RichText'
7import { Category } from 'src/components/Category'
8import Metatags from 'src/components/Metatags'
9import { ShareButtons } from 'src/components/ShareButtons'
10import { SanityImage, SanityImageObjectProps } from 'src/components/SanityImage'
11
12export interface Post {
13 title: string
14 authors: string[]
15 mainImage: SanityImageObjectProps
16 categories?: string[]
17 publishedAt: string
18 estimatedReadingTime: number
19 slug: string
20 introduction: PortableTextBlock[]
21 body: PortableTextBlock[]
22}
23
24const Post = ({ post }: { post: Post }) => {
25 const {
26 title,
27 authors,
28 categories,
29 mainImage,
30 publishedAt,
31 estimatedReadingTime,
32 introduction,
33 body,
34 slug
35 } = post
36
37 const rawIntro = richToPlainText(introduction)
38 const { theme } = useTheme()
39
40 return (
41 <>
42 <Metatags
43 title={title}
44 description={rawIntro}
45 image={urlFor(mainImage).url()}
46 path={`/post/${slug}`}
47 />
48 <article className={`prose w-full lg:prose-xl ${theme === 'dark' && 'prose-invert'}`}>
49 <h1 className="flex justify-center">{title}</h1>
50 <div className="flex flex-col space-y-2">
51 <span className="flex items-center justify-between">
52 <i>{formatAuthors(authors)}</i>
53 <ShareButtons />
54 </span>
55 <p>
56 <b>{formatDate(publishedAt)}</b>
57 </p>
58 <p>{`${estimatedReadingTime} min lesning`}</p>
59 </div>
60 {categories && (
61 <>
62 <div className="flex">
63 <span className="mr-2">
64 <i>Kategorier:</i>
65 </span>
66 {categories.map((category, index) => (
67 <Category key={index} value={category} />
68 ))}
69 </div>
70 </>
71 )}
72 <div className="text-xl font-bold">
73 <RichText value={introduction} />
74 </div>
75 {mainImage && <SanityImage image={mainImage} alt="mainImage" loading="lazy" />}
76 <RichText value={body} />
77 <ShareButtons className="justify-center" />
78 </article>
79 </>
80 )
81}
82
83export const getStaticPaths = async () => {
84 const paths: string[] = await client.fetch(
85 `*[_type == "post" && defined(slug.current)][].slug.current`
86 )
87
88 return {
89 paths: paths.map((slug) => ({ params: { slug } })),
90 fallback: 'blocking'
91 }
92}
93
94export const getStaticProps = async ({ params }: { params: { slug: string } }) => {
95 const { slug = '' } = params
96
97 const post: Post = await client.fetch(
98 groq`*[_type == "post" && slug.current == $slug][0]{
99 title,
100 "authors": authors[]->name,
101 "categories": categories[]->title,
102 "publishedAt": publishedAt,
103 "slug": slug.current,
104 "estimatedReadingTime": round(length(pt::text(body)) / 5 / 180 ),
105 mainImage,
106 introduction,
107 body
108 }`,
109 { slug: slug }
110 )
111
112 if (!post) return { notFound: true }
113
114 return {
115 props: {
116 post
117 },
118 revalidate: 60
119 }
120}
121
122export default Post

Deployment

Med disse teknologiene og verktøyene har du alt du trenger for å lage en egen innholdsrik, responsiv og søkermotorvennlig side! Det gjenstår kun å deploye løsningen til the world wide web. Vercel er selskapet bak Next.js, og har beleilig nok også en hosting plattform som gjør det lekende lett med deployment. Om ikke du har noen spesielle behov er det som regel bare å pushe repoet ditt til Github, lage en bruker hos Vercel, trykke på en knapp for å koble de to sammen, og da vil endringer til master-branchen automatisk deploye ny versjon.

Det var det. Håper det var nyttig og lærerikt!

Del: