Tilbake til bloggen
Guide13. mars 2026

React: Fra Flutter til React — arkitekturbeslutningen bak Nextbook

Nextbook startet som en Flutter-app. Cross-platform virket som den åpenbare veien — én kodebase, iOS, Android og web. I praksis viste det seg å være feil verktøy for jobben. Denne artikkelen handler om hvorfor vi migrerte til React, hvilke mønstre som fungerer i produksjon, og når du burde vurdere noe helt annet.

Hvorfor vi forlot Flutter

Flutter med Dart er et imponerende rammeverk for mobilapper. Problemet var at Nextbook ikke primært er en mobilapp — det er en læringsplattform, og læringsplattformer lever på web.

Web-ytelsen var ikke god nok. Flutter Web kompilerer til canvas-basert rendering, noe som betyr at tekst ikke er ekte DOM-tekst. For en plattform der brukere leser, søker og navigerer i innhold, var dette en dealbreaker. Lasttetider var trege, bundle-størrelsen var enorm, og brukeropplevelsen på web føltes som en app som prøvde å late som den var en nettside.

SEO var praktisk talt umulig. Kursinnhold bør være indekserbart. Studenter googler spørsmål og bør finne svar på plattformen. Med Flutter Web var alt skjult bak en canvas — søkemotorer så ingenting.

Økosystemet for innhold manglet. Vi trengte MDX for kursmateriell, syntax highlighting, matematiske formler, interaktive øvelser. Flutter-økosystemet hadde ikke modne løsninger for dette.

Beslutningen falt på React med TypeScript og Next.js. Vi fikk SSR ut av boksen, et massivt økosystem, og muligheten til å bygge en ordentlig web-first læringsplattform. Trade-off? Vi mistet den native mobilfølelsen. Men for Nextbook var web-egenskapene viktigere.

Slik strukturerer jeg React-applikasjoner

Etter flere år med React i produksjon har jeg landet på noen prinsipper som fungerer.

Komponentarkitektur. Skillet mellom presentational og container components er nyttig som mental modell, men i praksis bruker jeg det ikke som en streng regel. Hooks har gjort det meste av container-mønsteret overflødig. Det som betyr noe er at komponenter har ett ansvar — enten viser de noe, eller de koordinerer noe. Ikke begge deler.

State management. Min filosofi: start med useState. Når flere komponenter trenger samme state, løft den opp. Når løfting blir upraktisk, bruk Context. Jeg unngår Redux med mindre applikasjonen genuint har kompleks global state med hyppige oppdateringer fra mange kilder. For Nextbook bruker vi Context for autentisering og brukerpreferanser, useState for alt annet, og React Query for server state.

Custom hooks er den virkelige superkraften. Hooks lar deg pakke inn kompleks logikk — data-fetching, form-validering, keyboard-shortcuts — i gjenbrukbare enheter som er enkle å teste.

I Nextbook er komponenttreet organisert rundt domener: components/course/, components/quiz/, components/editor/. Delte UI-primitiver lever i components/ui/. Hver domene-mappe har sine egne hooks.

Kodeeksempler

Custom hook for datahenting

Et mønster vi bruker overalt i Nextbook — en hook som håndterer loading, error og success states:

function useCourse(courseId: string) {
  const [state, setState] = useState<ApiResult<Course>>({ status: 'loading' });

  useEffect(() => {
    let cancelled = false;

    async function load() {
      try {
        const data = await fetchCourse(courseId);
        if (!cancelled) setState({ status: 'success', data });
      } catch (err) {
        if (!cancelled) setState({ status: 'error', code: 'FETCH_FAILED', message: String(err) });
      }
    }

    load();
    return () => { cancelled = true; };
  }, [courseId]);

  return state;
}

cancelled-flagget forhindrer state-oppdateringer etter at komponenten er unmounted. Det er et lite detalj som unngår memory leaks og race conditions — den typen ting som aldri er et problem i utvikling men krasjer i produksjon.

Compound component for quiz-bygger

Nextbook har interaktive quizer inne i kursene. Compound component-mønsteret gir en ren API for å bygge dem:

function Quiz({ children }: { children: React.ReactNode }) {
  const [answers, setAnswers] = useState<Record<string, string>>({});
  return (
    <QuizContext.Provider value={{ answers, setAnswer: (id, val) => setAnswers(prev => ({ ...prev, [id]: val })) }}>
      <div className="space-y-6">{children}</div>
    </QuizContext.Provider>
  );
}

Quiz.Question = function Question({ id, prompt, options }: QuestionProps) {
  const { answers, setAnswer } = useQuizContext();
  return (
    <fieldset>
      <legend className="font-medium mb-2">{prompt}</legend>
      {options.map(opt => (
        <label key={opt.value} className="flex items-center gap-2">
          <input type="radio" name={id} checked={answers[id] === opt.value} onChange={() => setAnswer(id, opt.value)} />
          {opt.label}
        </label>
      ))}
    </fieldset>
  );
};

Fordelen med compound components er at forbrukerkoden blir deklarativ og lesbar. Du skriver <Quiz><Quiz.Question ... /></Quiz> i stedet for å passe et massivt konfigurasjonsobjekt.

Error boundary med gjenoppretting

Feil i React-komponenter bør ikke krasje hele appen. Error boundaries fanger feil i renderingstreet og viser en fallback:

class CourseErrorBoundary extends Component<Props, { error: Error | null }> {
  state = { error: null };

  static getDerivedStateFromError(error: Error) { return { error }; }

  render() {
    if (this.state.error) {
      return (
        <div className="p-6 bg-red-50 rounded-lg">
          <p>Noe gikk galt med kurset.</p>
          <button onClick={() => this.setState({ error: null })}>Prøv igjen</button>
        </div>
      );
    }
    return this.props.children;
  }
}

Vi bruker error boundaries rundt hver kurs-side i Nextbook. Hvis en quiz-komponent feiler, mister ikke brukeren hele siden — de kan prøve igjen eller navigere videre.

Mønstrene som gjorde migrasjonen verdt det

Tre ting ble radikalt bedre etter migrasjonen:

Server-side rendering. Kurssider rendres på serveren, noe som gir rask første innlasting og fungerer med søkemotorer. Studenter som googler et tema finner faktisk innholdet vårt nå.

Inkrementell adopsjon. Vi kunne migrere Nextbook side for side. React tvinger deg ikke til å gjøre alt på én gang — vi startet med de mest besøkte sidene og jobbet oss utover.

Økosystem-integrering. MDX for kursinnhold, Shiki for syntax highlighting, KaTeX for matematikk, React Query for data-synkronisering. Alt dette eksisterte allerede og fungerte godt sammen.

Når du IKKE bør bruke React

React er ikke alltid riktig verktøy, og det er viktig å innrømme.

Statiske innholdssider. Hvis du bygger en blogg eller dokumentasjon uten interaktivitet, er Astro eller Hugo enklere og raskere. Du trenger ikke et SPA-rammeverk for å vise Markdown.

Mobile-first apper. Hvis appen din primært lever på telefonen og trenger native gestures, kamera-integrasjon og offline-funksjonalitet, er React Native et alternativ — men ekte native (Swift/Kotlin) gir fortsatt best resultat for krevende mobile opplevelser.

Når teamet ditt kan noe annet. Hvis alle på teamet er produktive i Vue eller Svelte, er byttet til React sjelden verdt det. Rammeverket er et verktøy, ikke en identitet. Velg det som gjør teamet ditt mest effektivt.

Avsluttende tanker

Migrasjonen fra Flutter til React var den viktigste tekniske beslutningen vi tok for Nextbook. Det var ikke fordi React er "bedre" enn Flutter — det er fordi React var riktig verktøy for problemet vi faktisk løste. En læringsplattform som lever på web trenger web-native verktøy.

Hvis du står overfor en lignende beslutning: start med problemet, ikke med teknologien. Forstå hvor brukerne dine er, hva de trenger, og velg verktøyet som løser det best.

#react#frontend#arkitektur#nextbook

Nyhetsbrev

Få nye innlegg rett i innboksen.