Edit this page

pageContext

The pageContext object provides contextual information about the current page.

// /pages/product/@id/+data.js
 
export async function data(pageContext) {
  // Common built-in properties
  pageContext.urlParsed.pathname // /product/42
  pageContext.routeParams.id // 42
  pageContext.headers // { cookie: 'user-id=1337', ... }
 
  // Common custom properties
  pageContext.user // { name: 'John', id: 1337 }
  pageContext.initialStoreState // { todoList: [{ id: 1718872184291, text: 'Buy milk' }] }
}

The +data hook is explained at Guides > Data Fetching.

You can access pageContext everywhere:

It includes:

When the user navigates to a new page, a completely new pageContext object is created and the previous pageContext becomes obsolete — that's why it's called pageContext, not appContext. See Lifecycle for more information.

Built-in

Built-in properties:

  • pageContext.Page: the export { Page } or export default of the +Page.js file.

  • pageContext.data: the value returned by the data() hook, see also API > useData().

  • pageContext.routeParams: the route parameters. (E.g. pageContext.routeParams.movieId for a page with a Route String /movie/@movieId.)

  • pageContext.urlOriginal: the current URL.

    On the server-side, pageContext.urlOriginal is the value you passed at the server middleware:

    // Server middleware
    app.get('*', async (req) => {
      const pageContextInit = {}
      // `pageContext.urlOriginal` is defined here
      pageContextInit.urlOriginal = req.url
      const pageContext = await renderPage(pageContextInit)
      /* ... */
    })

    On the client-side:

    • When using Client Routing, the value of pageContext.urlOriginal is the browser's current URL (window.location.href).
    • When using Server Routing, the value of pageContext.urlOriginal is undefined (unless you use passToClient).
  • pageContext.urlPathname: alias for pageContext.urlParsed.pathname.

  • pageContext.urlParsed: URL information:

    {
      pathname: string
      pathnameOriginal: string
      search: Record<string, string> // AKA query parameters
      searchAll: Record<string, string[]>
      searchOriginal: null | string
      hash: string
      hashOriginal: null | string
      href: string
      origin: null | string
      protocol: null | string
      hostname: null | string
      port: null | string
    }

    Example:

    https://example.com/some-base-url/hello/s%C3%A9bastien?fruit=%C3%A2pple&fruit=orânge#%C3%A2ge
    
    {
      // Without Base URL, and decodes escaped characters.
      pathname: '/hello/sébastien',
      // With Base URL, and doesn't decode escaped characters.
      pathnameOriginal: '/some-base-url/hello/s%C3%A9bastien',
      search: { fruit: 'orânge' },
      searchAll: { fruit: ['âpple', 'orânge'] },
      searchOriginal: '?fruit=%C3%A2pple&fruit=orânge',
      hash: 'âge',
      hashOriginal: '#%C3%A2ge'
      // Without Base URL, and doesn't decode escaped characters.
      href: 'https://example.com/hello/s%C3%A9bastien?fruit=%C3%A2pple&fruit=orânge#%C3%A2ge',
      origin: 'https://example.com',
      protocol: 'https://',
      hostname: 'example.com', // 'localhost' if http://localhost:3000
      port: null // 3000 if http://localhost:3000
    }
  • pageContext.headers: The headers of the HTTP Request. As a string object (Record<string, string>) normalized by Vike, see HTTP Headers.

  • pageContext.headersOriginal: The headers of the HTTP Request. The original object provided by the server, see HTTP Headers.

  • pageContext.config: See API > meta.

  • pageContext.isHydration: Whether the page is rendered to HTML. When using Client Routing, the value is true for the first page the user navigates to, and false for any subsequent navigation. (When using Server Routing, the value is always true.) (If the page doesn't throw an error then it's equivalent to !pageContext.isClientSideNavigation, otherwise the error page is rendered and thus pageContext.isHydration is false whereas !pageContext.isClientSideNavigation is true.)

  • pageContext.isClientSide: Whether the page is being rendered on the client-side, or rendered on the server-side / pre-rendered. It's equivalent to: pageContext.isClientSide !== import.meta.env.SSR.

    ⚠️

    We recommend using Vite's import.meta.env.SSR whenever possible instead. Because Vite eliminates any code defined inside import.meta.env.SSR blocks from client-side bundles, saving potentially a lot of KBs. This isn't the case with pageContext.isClientSide.

    // This import is also removed if it's used only inside import.meta.env.SSR code blocks
    import someImport from './somewhere'
     
    if (import.meta.env.SSR) {
      // Vite removes this code block from client-side bundles.
      // ... server-side code ...
    }
  • pageContext.isPrerendering: Whether the page is being pre-rendered. The value is always false in development.

  • pageContext.isBackwardNavigation: Whether the user is navigating back in history. The value is true when the user clicks on his browser's backward navigation button, or when invoking history.back(). The isBackwardNavigation property only works with Client Routing. (The value is always null when using Server Routing.)

  • pageContext.previousPageContext: Upon client-side page navigation, you can use pageContext.previousPageContext to access the pageContext of the previous page. See Lifecycle.

  • pageContext.is404: If an error occurs, whether the error is a 404 Page Not Found or a 500 Internal Error, see API > Error Page.

  • pageContext.isClientSideNavigation: Whether the page was navigated by the client-side router. In other words, when using Client Routing, the value is false for the first page the user visits, and true for any subsequent navigation. (When using Server Routing, the value is always false.)

  • pageContext.abortReason: Set by throw render() and used by the error page.

  • pageContext.abortStatusCode: Set by throw render() and used by the error page.

  • pageContext.errorWhileRendering: The first error (if there is any) that occurred while rendering the page, see Integration > Error Tracking.

  • pageContext.isBaseMissing: Whether the Base URL is missing in the URL of the HTTP request made to the SSR server, see Guides > Base URL > Setup.

Custom

You can define custom pageContext properties. (See TypeScript for how to define their types.)

  • At renderPage():

    // Your Vike server middleware integration
    app.get('*', async (req) => {
      const pageContextInit = {
        urlOriginal: req.url,
        headersOriginal: req.headers,
     
        // ***************************************
        // **** Custom pageContext properties ****
        // ***************************************
     
        // Common use case: make information about logged-in user available at pageContext.user
        user: req.user,
     
        // Or any other value:
        // pageContext.someCustomProp
        someCustomProp: 'some-value'
      }
      const pageContext = await renderPage(pageContextInit)
      // ...
    })

    Setting pageContext.user is a common use case for integrating authentication tools, see Integration > Authentication > Integration.

  • At any Vike hook.

    // +someHook.js
    export function someHook(pageContext) {
      pageContext.someCustomProp = 'some-value' // Add or modify property
    }
  • At any UI component, by using usePageContext().

    // Inside a UI component
    const pageContext = usePageContext()
    pageContext.someCustomProp = 'some-value' // Add or modify property
  • At navigate({ pageContext: { someExtra: 'value' } }).

FAQ

Can I mutate pageContext?

Yes, it's a common practice to change/add pageContext properties at any time.

// Anywhere
pageContext.someProp = someValue

Can I use pageContext as a UI store?

Instead of using pageContext, we generally recommend using a proper UI state management tool such as React's useState(), Redux, Vue's ref(), Pinia, etc.

That said, there are use cases for using pageContext to store client-side state. For example to pass information from the previous page to the next during navigation.

See Lifecycle to understand whether using pageContext can make sense for your use case.

Can I check whether SSR is enabled?

On the server-side, you can tell whether SSR is enabled by checking whether pageContext.Page is set:

// +onAfterRenderHtml.js
 
export function onAfterRenderHtml(pageContext) {
  const isSSR = !!pageContext.Page
  if (isSSR) {
    // ...
  }
}

TypeScript

import type {
  // For code loaded in client and server
  PageContext,
  // For code loaded in client only
  PageContextClient,
  // For code loaded in server only
  PageContextServer
} from 'vike/types'

To extend and/or refine Vike's types PageContext/PageContextServer/PageContextClient, use the global interface Vike.PageContext:

declare global {
  namespace Vike {
    interface PageContext {
      // Type of pageContext.user
      user?: {
        name: string
        id: string
        isAdmin: boolean
      }
      // Refine type of pageContext.Page (it's `unknown` by default)
      Page: () => React.JSX.Element
    }
  }
}
 
// If you define Vike.PageContext in a .d.ts file then
// make sure there is at least one export/import statement.
// Tell TypeScript this file isn't an ambient module:
export {}

If you use Server Routing:

import type {
  // For code loaded in client and server
  PageContextWithServerRouting as PageContext,
  // For code loaded in client only
  PageContextClientWithServerRouting as PageContextClient,
  // For code loaded in server only
  PageContextServer
} from 'vike/types'

Lifecycle

The pageContext object is tied to the rendering of a page — whenever a page is (re-)rendered, a new pageContext object is created.

The lifecycle of the pageContext object is fairly straightforward and predictable on the client and server, but for pre-rendered pages it can be surprising (some pageContext properties may seem "outdated").

Server

On the server-side, a new pageContext object is created whenever a page is rendered to HTML. The pageContext object is discarded after the HTML is sent to the client. (But pageContext properties sent to the client via passToClient are preserved on the client-side.)

Client

On the client side, a new pageContext object is created whenever a page is rendered. In other words:

If data is fetched on the server-side, then some pageContext properties are fetched from the server, see pageContext.json requests.

On the client-side, you can access the pageContext of the previous render by using pageContext.previousPageContext.

Pre-rendering

Upon pre-rendering a page, the pageContext object used for pre-rendering the page to HTML is preserved and saved twice at:

  • dist/client/${urlOfThePage}/index.pageContext.json (see pageContext.json requests)
  • dist/client/${urlOfThePage}/index.html (see <script id="vike_pageContext">)

If you inspect dist/client/, you'll find a index.pageContext.json file for each pre-rendered page.

Since a pre-rendered page is built ahead of time, its server-side pageContext may be outdated by the time a user visits the page.

Problem: pageContext is outdated upon hydration

Upon hydration, some pageContext properties may appear incorrect or "outdated".

For example, if a user visits /products?filter=computer then pageContext.urlParsed.search is empty and ?filter=computer is missing.

That's because Vike uses the server-side pageContext.urlOriginal that was used to pre-render the URL /products which didn't have ?filter=computer.

In theory, Vike could update pageContext.urlParsed on the client-side to include ?filter=computer, but this would cause a hydration mismatch. That's why, upon hydration, Vike intentionally keeps the client- and server-side pageContext aligned.

Workarounds

To workaround the issue you can either:

  • Re-render the page after hydration, by using reload() with onHydrationEnd().
    // pages/products/+onHydrationEnd.js
     
    import { reload } from 'vike/client/router'
     
    export async function onHydrationEnd() {
      if (window.location.href.includes('?filter')) await reload()
    }
  • Or, if the number of ?filter= values isn't too large, you can use onBeforePrerenderStart() to pre-render all filter values: /products?filter=computer, /products?filter=car, ...

    You can then also use parameterized route /products/@filter instead of ?filter= if you prefer.

See also