Guide
Golden Path frontend
I denne artikkelen forsøker vi å definere en Golden Path som gjør det enkelt og raskt å komme i gang med frontendutvikling i NAV.
Golden hvaforno?
I NAV IT har vi stor frihet til selv å velge den teknologien som vi mener løser problemene våre på best måte. I en organisasjon på vår størrelse er det samtidig en fare for at mangfoldet av teknologier fører til høy kompleksitet for teamene. Stor variasjon gjør det vanskelig å orientere seg om hva andre driver med, og det blir vanskeligere å bygge gode verktøy.
En Golden Path er et forsøk på å definere en sti som gjør det enkelt å komme i gang med de vanligste usecasene, der de kjedelige tingene som bygg og deploy er løst for deg. Hvis flere bruker samme språk og rammeverk, kan man også lage biblioteker som abstraherer bort ting som autentisering eller styling av komponenter. Produktteam får dermed frigjort mental kapasitet til å løse problemet de egentlig skal løse, som i vårt tilfelle er velferd.
En Golden Path er et tilbud, ikke et krav. De som ønsker det kan avvike fra den når som helst.
Hvorfor denne guiden?
Denne guiden har som mål å få deg fra 0 til produksjon så raskt og enkelt som mulig. Appen vi skal bygge er basert på Next.js og deployes til applikasjonsplattformen vår NAIS. Ved å følge denne guiden får du stablet på beina en app med de vanligste tingene en innbyggerfacing NAV'sk app trenger, som Dekoratøren og komponenter fra designsystemet vårt, Aksel. Vi setter også opp nødvendige besvergelser for automatisk bygg og deploy, og vi viser hvordan du henter data fra andre tjenester.
Krav og forutsetninger
For å utvikle i NAV IT trenger du en "NAV-bruker" (som personallederen din allerede skal ha fått laget). De fleste utviklerne jobber på Mac, men PC med Linux eller Windows er også støttet. For å kunne koble til interne tjenester må denne maskinen ha en kjørende og "grønn" naisdevice. Du må ha installert Node.js, en teksteditor og Git. For å kunne bygge og kjøre appen lokalt på samme måte som den kjører på NAIS trenger du også Docker.
Vi bruker Git for versjonskontroll, og all koden vår ligger på GitHub. For å melde deg inn i organisasjonen vår der må du gå til https://myapps.microsoft.com/ og legge til appen "github.com/navikt".
Når du først er inne på myapps så legger du også til "Google Cloud Platform" for at det opprettes en Google-konto i ditt navn.
Hvert team i NAV har sitt eget avgrensede område på NAIS som de selv administrerer i utviklings- og produksjonsmiljøet. For å følge denne guiden kan du enten bruke et eksisterende team du er med i, eller du kan opprette et nytt team selv. For å opprette et nytt team klikker du på "Create team"-knappen i Console (krever naisdevice og Google-konto). Her må du oppgi navn på teamet, hvilken hensikt teamet har (NAV tillater kun gode hensikter), i tillegg til en Slack-kanal for teamet (om du ikke har en Slack-kanal fra før så har Jan-Kåre sagt at du kan bruke hans tullekanal: #jk-tullekanal). Du vil da automatisk få satt opp alt som trengs av tilganger hos Google, GitHub et al. Det kan gå opptil en halvtime før alt er på plass, men vanligvis går det ganske fort.
Med det unnagjort, så knekker vi i gang. Lykke til!
GitHub repo
Før vi kommer i gang med kodingen trenger vi et GitHub repo. For å lage det går du til github.com/navikt og trykker på den fine, grønne "New"-knappen. Velg et passende navn til repoet (vi valgte frontend-golden-path, så du må finne på noe annet), og velg at repoet skal være "Public" og ha MIT-lisens. Det aller meste av kode vi skriver i NAV ligger åpent tilgjengelig på internett, og dette trenger ikke være noe unntak.
For at magien som lar oss deploye fra dette repoet skal funke må NAIS-teamet ditt ligge inne som admin på repoet (om du mangler NAIS-team må du rykke tilbake til start; under "Krav og forutsetninger"). Gå til Settings i repoet ditt, under "Collaborators and teams" og trykk på "Add teams". Søk fram riktig team og velg Admin-rollen.
Klon repoet og dermed er vi endelig klare for å skrive kode!
React og Next.js
Så lenge komponentbiblioteket til designsystemet vårt er bygget i React er det det åpenbare valget av frontendteknologi. Hvilke andre teknologier vi skal velge er mindre åpenbart, men om vi vil gjøre serversiderendring av appen vår er det en fordel å velge et metarammeverk. Her er alternativene mange, og det er team i NAV som av ulike grunner har valgt enten Remix eller Astro. Det mest populære her på huset er allikevel Next.js, så for å best kunne støtte oss på resten av miljøet så går vi for det.
For å generere en Next-app i katalogen du står i bruker du følgende kommando i det nylig klonede repoet ditt:
(create-next-app
resolver i skrivende stund til 14.2.4)
Svar ja på spørsmål om TypeScript (NAV <3 TypeScript).
Svar ja på spørsmål om ESLint (Linting er nice).
Svar nei på spørsmål om Tailwind CSS (Mer om styling lengre ned i artikkelen).
Svar nei på spørsmål om du vil bruke `src/` directory (trengs ikke).
Svar nei på spørsmål om du vil bruke App Router (Dekoratøren funker ikke med App Router enda, men det jobbes med en løsning).
Svar nei på spørsmål om custom default import alias (trengs ikke).
Og vipps har du en flunkende ny Next-app! 🎉
For å starte appen din kjører du npm run dev
.
Før vi går videre er det på sin plass med en commit og push.
Designsystemet Aksel
Neste steg på veien mot en fullverdig NAV-app er å ta i bruk designsystemet Aksel. Da trenger vi å installere følgende pakker:
Stiler
Noen ganger føles det som at det er like mange måter å skrive stiler på som det er frontendutviklere i verden. Som alltid vil ulike tilnærminger ha ulike fordeler og ulemper, men for stiler spesifikt har valg av teknologi ganske lite å si; vi kan lykkes med semantiske klasser eller utilityklasser, Tailwind eller SASS. Vi må velge noe, og det enkleste er ofte det beste. Å unngå ekstra abstraksjonsnivåer ved å benytte oss av teknologien som er en del av nettleseren har mange fordeler og få ulemper, spesielt når moderne nettlesere etterhvert har implementert CSS-features som flexbox, grid, variabler og nesting.
Tilnærmingen vår er derfor å holde oss ganske nære specen ved å skrive god, gammaldags CSS, og å ta i bruk CSS modules for å få isolasjon av stiler per komponent. CSS modules kommer bundlet med Next, så her trenger vi ikke installere eller konfigurere noe ekstra.
Globale stiler
For å ta i bruk designsystemets stiler må vi importere `@navikt/ds-css` et eller annet sted så den havner høyt opp i stilarket. Fordi vi ikke har noen andre globale stiler bytter vi ut import '@/styles/globals.css'
med import '@navikt/ds-css'
i pages/_app.tsx. Da kan styles/globals.css slettes.
Bruk av tokens
Aksel kommer med en rekke design tokens som lar oss referere til stiler i designsystemet fra vår egen CSS. Bytt ut hele innholdet i Home.module.css med følgende:
Om du så åpner appen din igjen ser du at innholdet har fått en skjønn kommunegrå bakgrunn.
Bruk av komponenter
Egne stiler er vel og bra, men desto bedre er det om vi bruker hele komponenter fra Aksel. Ved å bytte ut innholdet i pages/index.tsx med følgende tar vi i bruk Button-komponenten fra Aksel:
Ikoner
Aksel kommer også med et stort sett ikoner. De installerer du sånn:
Nå synes jeg du har vært så flink at du fortjener å få en tommel opp! Legg inn koden under i pages/index.tsx for å sende inn et ikon til Button:
Nå er det igjen tid for å committe og pushe.
Dekoratøren
Dekoratøren er header og footer på nav.no. Den inneholder diverse globale scripts, og den håndterer bl.a. innlogging og språkvalg. Før vi kommer så langt som å bruke den er det en liten ting vi må fikse.
GitHub Packages
For å installere pakker med @navikt-scope (med unntak av Aksel) må vi konfigurere npm til å bruke GitHub Packages. Det vi trenger da er en .npmrc
-fil, f.eks. i home directory, med følgende innhold:
For å generere _authToken går du til developer settings på GitHub, trykker på "Generate new token" og velger "Generate new token (classic)". Det eneste scopet dette tokenet skal ha er read:packages
.
Siden dette tokenet blir liggende i klartekst på disken din er det viktig at det har så få rettigheter som mulig. Kun read:packages
trengs for å laste ned pakker.
Kopier tokenet og legg det i miljøvariabelen NODE_AUTH_TOKEN
.
For at denne miljøvariabelen alltid skal være tilgjengelig legger du linja over i shellet ditt sin rc-config-fil (f.eks. ~/.bashrc
).
Konfigurere SSO
For å autorisere tokenet for navikt-orgen må du konfigurere SSO. For å gjøre det trykker du på "Configure SSO" på tokenet du nettopp genererte, og velger "navikt".
Installere Dekoratøren Moduler
Dekoratøren kjører som en egen app på NAIS, noe som bl.a. gjør det mulig å deploye endringer til den uten at vi må gjøre noe i vår app. For å gjøre det enklere å integrere med Dekoratøren har vi laget biblioteket nav-dekoratoren-moduler. Sånn installerer du den:
Vi trenger Dekoratøren på alle sider i appen vår, så lim inn denne koden i pages/_document.tsx.
Som du ser tar vi i bruk Page
-komponenten fra Aksel, som gir oss sidelayout med sentrering av innhold og bunnjustert footer.
Om du nå åpner appen din kan du se at header og footer fra nav.no er på plass. Ingen liten bragd, spør du meg.
Commit og push.
Sidemaler
Med dekoratøren på plass kan vi ta i bruk sidemaler fra Aksel. Lag en 404.tsx-fil i pages og legg inn denne koden:
For å sjekke at det funka går du til en sti som ikke finnes, f.eks. localhost:3000/foo.
Så enkelt kan det altså gjøres. Commit og push.
Bygge og kjøre appen lokalt
Nå som den skinnende nye appen er klar for verden må den bygges og klargjøres for kjøring. På NAIS kjører applikasjoner i Docker-containere, så det første vi må gjøre er å bygge et Docker-image.
For å bygge en Next-app som skal kjøre i Docker vil vi konfigurere Next til å spytte ut en "standalone" app. For å gjøre det setter du output
til 'standalone' i next.config.mjs:
Opprett så en Dockerfile
med følgende innhold på rota av prosjektkatalogen:
Med dette på plass kan vi bygge et docker image. Sørg for at du har miljøvariabelen NODE_AUTH_TOKEN
med github-tokenet du genererte tidligere.
Og denne kan kjøres lokalt:
Og dermed har vi en fiks ferdig NAV-app som kjører i Docker og kan beskues på http://localhost:3000. Herlighet!
Commit og push.
Deploy
For å deploye appen din til NAIS må du bruke en GitHub Action som heter nais/deploy/actions/deploy@v2
. Denne bruker et konsept som heter "workload identity", dvs. at GitHub workflowen identifiserer seg vha. et token utstedt og signert av GitHub. For å tillate deploy fra et repository må man gå til https://console.nav.cloud.nais.io/team/<mitt-team>/repositories og klikke på "Authorize".
GitHub Action
Denne fila definerer en GitHub Action for bygg og deploy. Hvis du legger den på stien .github/workflows/build-and-deploy.yml så kjører den automatisk hver gang du pusher til main-branchen på GitHub. Husk å legge inn ditt teamnavn der det står *teamnavn*.
Nais YAML
I build-and-deploy.yml peker vi på en nais.yml (under Deploy > env > RESOUCE), som beskriver NAIS-applikasjonen din. Legg fila under på rot i prosjektet ditt. Her må du huske bytte ut de tre metadata-feltene som identifiserer appen og teamet ditt, og du må definere en ingress for appen din helt nederst.
Commit, push og vent i spenning på om bygg og deploy går gjennom. Progresjon på bygg kan du følge med på under Actions i repoet ditt på Github. Om alt er som det skal tar det bare få minutter før du kan se appen din på urlen du definerte under ingress i nais.yml.
CDN
Nå er appen din deployet, og alt funker, men mye kan fortsatt bli bedre. Ett forbedringspotensiale er at det er fryktelig ineffektivt å involvere Next-serveren i henting av statiske ressurser. Ikke bare er det treigt, helt uten at du vet det har du faktisk også nedetid hver gang du deployer.
Løsning er heldigvis like enkel som den er genial: last opp statiske ressurser til vårt Content Delivery Network (CDN)!
CDNification krever kun to steg: config av en asset prefix i Next og opplasting av ressurser i en GitHub action.
Asset prefix
Det første først: For at Next skal vite at statiske ressurser hentes fra CDN i prod må vi sette assetPrefix i next-configen. Husk å bytte ut *teamnavn*.
GitHub action
Deretter legger du inn steg i .github/workflows/build-and-deploy.yml for å bygge statiske ressurser og gjøre opplasting til CDN ved hvert bygg. Stegene skal inn før "Deploy to nais". Husk å bytte ut *teamnavn*.
For at `target: export` skal funke trenger vi et byggsteg i dockerfila vår for å eksportere statiske ressurser. Disse to linjene legger du nederst i Dockerfile:
Før vi sier oss helt ferdig med CDN kan vi rydde litt, for denne linja i Dockerfile er ikke lenger nødvendig, så den kan fjernes:
Gitt at noen har somla seg til å merge PRen din får du nå nedetidsfri deploy og raskere lasting. Gratulatore! 🎉
Commit, push, og vent til bygget går gjennom. Når det er ferdig kan du gå til URLen appen din er deployet til og sjekke i nettverkstabben i devtools at css- og js-filer hentes fra cdn.nav.no.
Authnz
Authnz en en fancy forkortelse for "authentication" and authorization". De fleste appene vi lager krever at brukeren er innlogget og at informasjonen i dem er tilgangsstyrt på en eller annen måte. I NAV har vi valgt å standardisere på OAuth og OIDC for dette med "Entra ID" og ID-porten som identitetstilbydere for hhv. ansatte og publikum. Heldigvis trenger du ikke å ha svart belte i noen av disse teknologiene for å bruke dem. For å legge OIDC-autentisering på appen din trenger du bare å enable det med noen få linjer ekstra i nais-manifestet.
Eksempel:
Plattformen vil da automatisk validere at Bearer tokens har gyldig signatur, tokenets claims må dere selv validere. Disse claims-ene kan dere så bruke som beslutningsgrunnlag for avgjørelser om tilgang.
Hente data fra andre tjenester
Som oftest trenger man å hente data fra andre tjenester for å kunne svare på en request. Dette vil si at man fra sin "backend for frontend" gjør HTTP-kall videre på vegne av sluttbrukeren. For å kunne utføre disse kallene må et par ting på plass: de appene som skal snakke med hverandre må sette opp "access policies", og man må "veksle inn" access tokens med et nytt som er gyldig for den tjenesten man skal kalle.
I utgangspunktet har apper på nais ikke lov til å åpne nettverksforbindelser, all trafikk til og fra andre apper må eksplisitt tillates. Dette gjøres ved hjelp av network policies i nais-manifestet. Appen som initierer kallet må ha en outbound policy:
Appen som kalles må sette opp en tilsvarende inbound policy:
Inbound policy må settes opp av teamet som eier den aktuelle appen.
Hvis du skal kalle tjenester ute på internett må disse også eksplisitt tillates:
Når du nå kan kalle favorittjenesten din må du kunne autentisere deg. Dette gjøres vha en teknikk som kalles "OAuth2 Token Exchange". Det innebærer at access tokenet du mottok må veksles inn i et nytt som er "scopet" for tjenesten du skal kalle videre. Fordelen med en slik løsning er at man slipper å ha "servicebrukere" med kjempevide tilganger og at skadepotensialet blir mye mindre dersom et slikt token skulle komme på avveie. En slik løsning sørger også for at identiteten til sluttbrukeren som utførte det originale kallet blir med hele veien, når man benytter servicebrukere går denne informasjonen tapt uten ekstra kompenserende tiltak.
Kode for å gjøre slik token-veksling finnes i biblioteket @navikt/oasis. Dette biblioteket er opprinnelig skrevet for bruk i dagpengeteamet sine apper, men er nå gjort generelt for å kunne benyttes av flere. I GitHub-repoet er det også en eksempelapp som viser hvordan biblioteket brukes.
Oppsummering
Da skal byggesteinene du trenger for å få stablet på beina en brukandes frontend-app i NAV være på plass. Hvis du ønsker å fordype deg i noen av temaene som er nevnt her kan nais-docen og de andre artiklene her på Aksel være greie plasser å starte. Ellers er det alltid noen som kan svare på spørsmål på Slack i feks. #frontend.
Happy coding!
Medvirkende