# Evästesuostumuksen käsittely yhden sivun sovelluksissa

> Näin hallitset evästesuostumusta Reactissa, Vuessa ja muissa SPA-sovelluksissa: jaettu suostumustila, reittimuutokset, SSR-hydraatio, upotukset ja välitön peruutus.

Source: https://www.cookiesh.eu/fi/blog/consent-in-single-page-apps  
Published: 2026-08-11

## Key takeaways

- Yhden sivun sovelluksessa suostumus on sovelluksen tilaa: tallenna se kerran, jaa se jokaiselle komponentille ja reagoi muutoksiin ilman sivun uudelleenlatausta.
- Aseta jokaisen muun kuin välttämättömän kategorian oletukseksi evätty, jotta ennen tallennetun valinnan latautumista suoritettava koodi ei voi vuotaa tietoja.
- Lähetä virtuaaliset sivunäytöt reittimuutosten yhteydessä vain, jos kävijä on hyväksynyt analytiikan.
- Lue suostumuseväste palvelinrenderöinnin aikana tai renderöi banneri vain asiakaspuolella, jotta vältät hydraatiovirheet ja bannerin välähtelyn.
- Suostumuksen peruuttamisen on oltava yhtä helppoa kuin sen antamisen, ja sivun uudelleenlataus on usein siistein tapa pysäyttää jo käynnissä olevat skriptit.

Yhden sivun sovelluksessa (SPA) sivu ei koskaan lataudu kokonaan uudelleen, joten evästesuostumuksen on elettävä sovelluksen tilassa eikä kerran suoritettavassa skriptissä. Tallenna kävijän valinta, jaa se jokaiselle komponentille, aktivoi tai estä skriptit sen muuttuessa, lähetä reittimuutosten sivunäytöt vain suostumuksella ja varmista, että peruutus tulee voimaan heti eikä vasta seuraavalla sivulatauksella.

## Miksi evästesuostumus on hankalampi yhden sivun sovelluksessa?

Useimmat suostumusratkaisut olettavat kokonaisia sivulatauksia, kun taas SPA latautuu kerran ja vaihtaa sitten näkymiä JavaScriptillä. Tämä rikkoo neljä oletusta:

- **Skriptit latautuvat kerran.** Jos analytiikka estettiin latauksen yhteydessä ja kävijä hyväksyy myöhemmin, mikään sivulataus ei ota sitä käyttöön. Se on aktivoitava paikan päällä.
- **Sivunäytöt ovat virtuaalisia.** Reittimuutokset eivät lataa sivua uudelleen, joten reitittimen on raportoitava sivunäytöt, ja sen on tarkistettava suostumus ensin.
- **Valinnat muuttuvat kesken istunnon.** Kävijä voi hyväksyä, selata ja sitten peruuttaa suostumuksensa alatunnisteen linkistä, kun skriptit ovat jo käynnissä muistissa.
- **Palvelinrenderöinti tuo mukanaan hydraation.** Next.js:n tai Nuxtin kaltaisissa kehyksissä palvelimen HTML:n on vastattava ensimmäistä asiakaspuolen renderöintiä, mutta palvelin ei välttämättä tiedä kävijän valintaa.

## Miten suostumustila kannattaa mallintaa SPA:ssa?

Käsittele suostumusta pienenä tilasäilönä, josta jokainen sovelluksen osa lukee, ja jossa on subscribe-metodi, jotta komponentit ja skriptit reagoivat muutoksiin. Aseta jokaisen muun kuin välttämättömän kategorian oletukseksi evätty, jotta ennen tallennetun valinnan latautumista suoritettava koodi ei voi koskaan vuotaa tietoja.

```ts
// consent-store.ts
export type Category = 'necessary' | 'preferences' | 'analytics' | 'marketing';
export type Consent = Record<Category, boolean>;

export const DENIED: Consent = {
  necessary: true, preferences: false, analytics: false, marketing: false,
};

let state: Consent = DENIED;
const listeners = new Set<() => void>();

export const consentStore = {
  get: (): Consent => state,
  set(next: Partial<Consent>) {
    state = { ...state, ...next, necessary: true };
    listeners.forEach((notify) => notify());
  },
  subscribe(listener: () => void) {
    listeners.add(listener);
    return () => { listeners.delete(listener); };
  },
};
```

Todellisessa sovelluksessa `set` myös tallentaisi valinnan ensimmäisen osapuolen evästeeseen, kirjaisi sen suostumuslokiin ja päivittäisi Google Consent Moden kutsulla `gtag('consent', 'update', …)`. Reactissa `useSyncExternalStore` yhdistää säilön komponentteihin, ja samalla hookilla voi pidätellä evästeitä asettavia upotuksia:

```tsx
// consent.tsx
import { useSyncExternalStore } from 'react';
import { consentStore, DENIED } from './consent-store';

export function useConsent() {
  return useSyncExternalStore(consentStore.subscribe, consentStore.get, () => DENIED);
}

export function VideoEmbed({ videoId }: { videoId: string }) {
  const { marketing } = useConsent();

  if (!marketing) {
    return (
      <div className="embed-placeholder">
        <p>Tämä video on YouTuben palvelussa, joka voi asettaa markkinointievästeitä.</p>
        <button type="button" onClick={() => consentStore.set({ marketing: true })}>
          Salli markkinointievästeet ja toista
        </button>
      </div>
    );
  }

  return (
    <iframe
      src={`https://www.youtube-nocookie.com/embed/${videoId}`}
      title="Tuotevideo"
      allowFullScreen
    />
  );
}
```

`useSyncExternalStore`-funktion kolmas argumentti on tilannevedos, jota käytetään palvelinrenderöinnin ja hydraation aikana. Kun siinä palautetaan `DENIED`, palvelin ja ensimmäinen asiakaspuolen renderöinti ovat yhtä mieltä, ja React renderöi uudelleen todellisella tilalla heti hydraation jälkeen. Vuessa saman roolin hoitaa reaktiivisen `ref`-arvon ympärille rakennettu composable.

Skriptit, jotka eivät ole komponentteja, voivat tilata säilön muutokset suoraan ja ladata ne dynaamisella `import()`-kutsulla, kun niiden kategoria on hyväksytty. Oppaassamme [skriptien estämisestä suostumukseen asti](/fi/blog/block-scripts-until-consent) käsitellään aktivointitapoja.

## Miten reittimuutoksia seurataan vain suostumuksella?

Lähetä reitittimestä virtuaalinen sivunäyttö jokaisen siirtymän jälkeen ja tarkista suostumus juuri sillä hetkellä. Jos kävijä antaa analytiikkasuostumuksen kesken istunnon, nykyisen sivun kirjaaminen on perusteltua, koska hyväksyntä alkaa siitä.

```tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useConsent } from './consent';
import { trackPageView } from './analytics';

export function PageViewTracker() {
  const { pathname } = useLocation();
  const { analytics } = useConsent();

  useEffect(() => {
    if (analytics) trackPageView(pathname);
  }, [pathname, analytics]);

  return null;
}
```

Varo tuplalaskentaa. Jotkin analytiikkatyökalut voivat lähettää sivunäyttöjä automaattisesti selainhistorian muuttuessa; Google Analytics 4 tarjoaa tämän tehostetun mittauksen (enhanced measurement) asetuksena. Käytä joko automaattista asetusta tai omaa seurantakomponenttiasi, älä molempia.

## Miten vältän hydraatiovirheet ja bannerin välähtelyn?

Päätä, missä kävijän valinta tiedetään ensimmäisessä renderöinnissä. Vaihtoehtoja on kaksi hyvää:

- **Lue suostumuseväste palvelimella.** Evästeet lähetetään jokaisen pyynnön mukana, joten palvelimella renderöidyt sivut voivat näyttää tai ohittaa bannerin oikein ensimmäisestä tavusta lähtien. Tämä toimii vain, jos HTML:ää ei tallenneta välimuistiin ja jaeta kävijöiden kesken.
- **Renderöi banneri vain asiakaspuolella.** Staattisesti generoiduilla tai CDN-välimuistissa olevilla sivuilla älä renderöi palvelimella mitään, vaan näytä banneri, kun sovellus on liitetty sivulle. Sijoita se kiinteäksi päällyskerrokseksi, jotta myöhäinen ilmestyminen ei aiheuta asettelun siirtymää; lue lisää artikkelista [evästebannerit ja Core Web Vitals](/fi/blog/cookie-banners-core-web-vitals).

Kummassakin tapauksessa valinnan muistava eväste tai tallennustilan merkintä katsotaan yleensä välttämättömäksi valinnan noudattamiseksi; katso [mikä lasketaan välttämättömäksi evästeeksi](/fi/blog/strictly-necessary-cookies).

## Miten suostumuksen peruuttaminen kesken istunnon käsitellään?

Tee peruuttamisesta välitöntä ja yhtä helppoa kuin suostumuksen antamisesta, kuten tietosuoja-asetuksen (GDPR) 7 artiklan 3 kohta edellyttää. Käytännössä se tarkoittaa pysyvää ”Evästeasetukset”-linkkiä, yleensä alatunnisteessa, joka avaa asetukset uudelleen ilman sivulatausta. Kun kävijä peruuttaa kategorian:

1. Päivitä säilö ja tallenna uusi valinta.
2. Jos käytät Google-tageja, lähetä `gtag('consent', 'update', …)` niin, että asiaankuuluvat signaalit ovat tilassa `'denied'`.
3. Lopeta seurantafunktioiden kutsuminen; säilön ehtoihin sidottu koodi tekee tämän automaattisesti.
4. Poista peruutetun kategorian asettamat ensimmäisen osapuolen evästeet, kun tiedät niiden nimet ja verkkotunnukset. Skriptisi eivät voi poistaa muille verkkotunnuksille asetettuja evästeitä.
5. Lataa sivu uudelleen, jos jokin peruutetuista skripteistä on jo käynnissä. Kirjastot säilyttävät ajastimia, kuuntelijoita ja muistissa olevia tunnisteita, ja uudelleenlataus on ainoa luotettava tapa tyhjentää ne.

Peruuttamisen jälkeinen uudelleenlataus on kohtuullinen kompromissi: se on harvinainen, ja vaihtoehtona on suorittaa koodia, jonka suorittamiseen sinulla ei enää ole lupaa.

## Yhteenveto

SPA:ssa suostumus on tilaa: oletuksena evätty, yksi jaettu säilö, skriptit ja upotukset sen varassa, sivunäytöt vain suostumuksella ja peruutus voimaan heti. Cookieshin kaltaiset työkalut voivat hoitaa suuren osan tästä putkityöstä React SDK:lla, joka tarjoaa `CookieshProvider`-komponentin ja `useConsent()`-hookin, sekä `consent:updated`-tapahtumalla kaikkea muuta varten.

*Tämä artikkeli on yleistä tietoa eikä oikeudellista neuvontaa. Kun teet omaa tilannettasi koskevia päätöksiä, käänny pätevän tietosuoja-asiantuntijan puoleen.*

## FAQ

### Pitääkö evästebanneri näyttää uudelleen jokaisen reittimuutoksen jälkeen?

Ei. Näytä se, kunnes kävijä tekee valinnan, tallenna valinta ja tarjoa pysyvä linkki asetuksiin. Kysy uudelleen vain, jos käyttötarkoituksesi tai toimittajasi muuttuvat olennaisesti tai tallennettu valinta vanhenee.

### Voinko tallentaa suostumusvalinnan localStorageen evästeen sijaan?

Molemmat toimivat selaimessa, mutta eväste lähetetään myös palvelimellesi jokaisen pyynnön mukana, joten palvelimella renderöidyt sivut voivat näyttää oikean tilan jo ensimmäisessä renderöinnissä. localStoragea voi lukea vain asiakaspuolen koodi sovelluksen latauduttua.

### Hoitavatko kehysten skriptikomponentit, kuten next/script, suostumuksen puolestani?

Eivät. Apuvälineet, kuten Next.js:n Script-komponentti, määräävät, milloin skripti latautuu suhteessa hydraatioon, eivät sitä, onko kävijä hyväksynyt sen. Renderöi ne ehdollisesti suostumustilasi perusteella, jotta niitä ei lisätä sivulle ennen hyväksyntää.

## Sources

- [Directive 2002/58/EC (ePrivacy Directive), EUR-Lex](https://eur-lex.europa.eu/eli/dir/2002/58/oj)
- [Regulation (EU) 2016/679 (GDPR), EUR-Lex](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
- [Google: Consent mode overview](https://developers.google.com/tag-platform/security/guides/consent)
