Routing

Similarly to Next.js, we define a new page by creating a new +Page.jsx file.

// /pages/index/+Page.jsx
// Environment: browser and server
 
import React from "react";
import { Counter } from "../components/Counter";
 
export { Page };
 
function Page() {
  return <>
    This page is rendered to HTML and interactive: <Counter />
  </>;
}

By default, Vike does Filesystem Routing.

FILESYSTEM                  URL
/pages/index/+Page.jsx      /
/pages/about/+Page.jsx      /about

We can also define a page's route with a Route String (for parameterized routes such as /movies/@id) or a Route Function (for full programmatic flexibility).

// /pages/index/+route.js
 
// Note how the two files share the same folder `/pages/index/`; this is how Vike
// knows that `/pages/index/+route.js` defines the route of `/pages/index/+Page.jsx`.
 
// Route Function
export default pageContext => pageContext.urlPathname === '/';
 
// If we don't create a `+route.js` file then Vike does Filesystem Routing

Render Control

Unlike Next.js, we control how our pages are rendered.

// /renderer/+onRenderHtml.jsx
// Environment: server
 
import ReactDOMServer from "react-dom/server";
import React from "react";
import { escapeInject, dangerouslySkipEscape } from "vike/server";
 
export { onRenderHtml };
 
async function onRenderHtml(pageContext) {
  const { Page, data } = pageContext;
  const viewHtml = ReactDOMServer.renderToString(
    <Page {...data} />
  );
 
  const title = "Vite SSR";
 
  return escapeInject`<!DOCTYPE html>
    <html>
      <head>
        <title>${title}</title>
      </head>
      <body>
        <div id="page-view">${dangerouslySkipEscape(viewHtml)}</div>
      </body>
    </html>`;
}
// /renderer/+onRenderClient.jsx
// Environment: browser
 
import ReactDOM from "react-dom/client";
import React from "react";
 
export { onRenderClient };
 
async function onRenderClient(pageContext) {
  const { Page, data } = pageContext
  ReactDOM.hydrateRoot(
    document.getElementById("page-view"),
    <Page {...data} />
  );
}

This control enables us to easily and naturally integrate any tool we want (Redux, GraphQL, Service Worker, Preact, ...).

Note how we defined the files onRenderClient.jsx and onRenderHtml.jsx in a directory called /renderer/: that way, we tell Vike to apply the onRenderHtml() and onRenderClient() hooks to all our pages.

This means we can now create a new page just by defining a new +Page.jsx file (the +route.js file is optional).

Plus files in /renderer/ can be overridden. For example, we can override the onRenderHtml() and onRenderClient() hooks for rendering some of our pages with a completely different UI framework such as Vue.

Data Fetching

Let's now have a look at how to fetch data.

// /pages/star-wars/movie/+Page.jsx
// Environment: browser and server
 
import React from "react";
 
export { Page };
 
function Page(data) {
  const { movie } = data;
  return <>
    <h1>{movie.title}</h1>
    <p>Release Date: {movie.release_date}</p>
    <p>Director: {movie.director}</p>
  </>;
}
// /pages/star-wars/movie/+route.js
// Environment: server
 
// Route String
export default "/star-wars/@movieId";
// /pages/star-wars/movie/+data.js
// Environment: server
 
import fetch from "node-fetch";
 
export async function data(pageContext) {
  // The route parameter of `/star-wars/@movieId` is available at `pageContext.routeParams`
  const { movieId } = pageContext.routeParams;
 
  // By default, +data.js files are loaded and executed only on the server-side
  // which means we can use SQL/ORM queries here.
  const response = await fetch(`https://star-wars.brillout.com/api/films/${id}.json`)
  let movie = await response.json();
 
  // Our render and hydrate functions we defined earlier pass `pageContext.data` to
  // the root React component `Page`; this is where we define it.
  return {
    movie
  };
}

That's it for the tour and we have actually already seen most of Vike's interface; not only is Vike flexible but it's also simple to use!