Vike – a modern SSR framework

Hi all. I am the lead frontend developer at 21Yard. We are developing a service for finding construction contractors.

I came to the project as a greenhorn, who knew little about SEO-promotion of a product, but life made its own adjustments, and now I want to talk about the modern ssr framework -vike, show its main aspects.

PS The article is intended primarily for young and green people, but it will also be useful for seasoned kalachs.

Motivation for writing an article

Vike is a young framework that has not yet reached version 1.0.0. It has good documentation in English, but, unfortunately, there are no Russian-language guides for it. I had to figure out many micro-moments myself, because… there was no one to even ask. I hope this article will open up an alternative to next for many, and will help others understand the basic principles of this wonderful framework.

A little more introduction…

When I came to the project, I got down to business with enthusiasm. At the time I started my work, we already had an Internet portal written in PHP. Unfortunately, it was written in an outdated framework, so the decision was made to rewrite it from scratch using something modern – the choice fell on React. However, marketing went along with coding. An SEO specialist was brought in to work, according to whose instructions I needed to make micro-edits to the old portal. Then I found out what SEO is and that it requires ssr…

Help for the little ones

A regular React application created via npm create vite (or, God forbid, npx create react app) after assembly is a set of statics – index.html, a pack of styles, a pack of toad script. This means that the user who requested the resource receives an empty html file, after loading which the generation of visible content begins. Unfortunately, search robots are lazy and don’t really want to wait until all the executable code is executed and the DOM is replenished with new nodes, so the most we can offer it is a couple of static meta tags inside head. This means that in search results we can only appear as one single link (after all, internal routing will also be ignored). This approach is called CSR – Client Side Rendering

To make it possible to index our site, we need all the html to be generated on the server side, and only then sent to the search robot, with all the meta tags, headings, articles, links and other delights of web development. This is called SSR – Server Side Rendering. To make React do SSR, an additional framework is required.

This is exactly what I found out when I wanted to learn more about SEO. Then it was decided to urgently implement some kind of SSR framework before the development went too far. Of these, I only knew Next.js, the reviews of which in the React telegram community were not to my liking. I didn’t really want to tinker with a half-finished product, switch to another private label, and so on, so I went looking. The project used Effector, and it was this community that I decided to ask about a similar solution. And they recommended Vike to me.

What kind of animal?

Vike is a stupidly flexible framework based on vite. It allows you to create SSR, CSR, SSG, as well as hybrid and isomorphic (at the first page request, the content is generated on the server side, and with further routing everything happens dynamically on the client) web applications.

What is flexibility?

  • Versatility – it can be used to make SSR, CSR, SSG, hybrid and isomorphic applications

  • Freedom of choice – it offers its own adapter prepared for each UI framework (react, vue, angular, etc.), but does not prohibit you from writing it yourself.

  • Powerful routing – allows you to configure routing based on folder architecture, or write more complex routing logic for each page separately

  • Clear division of responsibilities – protection from unauthorized access, data sampling, setting the contents of the header, title, description, layout, effects of the beginning and end of rendering, etc. – each stage of rendering is regulated individually

  • Compatibility with STM – with the isomorphic approach, no manipulations are required at all for the correct operation of STM; There are adapters for a strictly SSR approach.

  • Prospects – vike is actively developing, new features and cool things are appearing, and if you have any questions or suggestions, you can chat with its maintainers on discord

Will there be any disadvantages?

  • 0 articles on the topic in Russian. The reason this article exists

  • Difficulties with backward compatibility. It won’t be noticeable for new users, but I described my pain in the paragraph with layouts

  • Setting up from scratch is a little unclear; the documentation does not provide comprehensive answers to emerging questions, so you have to think about it specifically. Templates are filled with unnecessary garbage; they have to be cleaned before development.


Creating a Project

The documentation recommends creating a project using Bati, the vike template customization tool.

Template Customizer Appearance

Template Customizer Appearance

It’s very convenient – you don’t have to bother pulling the basic dependencies yourself. I used the React + Tailwind + Express combination on the project, and we’ll use it here too!

Project structure (without config files)

Project structure (without config files)

The project structure is not very different from a regular vite template, we will look at the main thing:

express-entry – the entry point to the application, a regular node server. In my experience, if we are doing a purely frontend, then this file does not change often

express-entry.ts

express-entry.ts

vike-handler – entry point into the rendering process. In old templates, this code was not removed from express-entry

server/vike-handler.ts

server/vike-handler.ts

Pages folder – the root of the folder contains settings common to all pages, as well as the pages themselves.

Folder with pages

Folder with pages

The path to the page by default is built similar to the path to the file, i.e. the page http://example.com/products/edit will correspond to the path pages/products/edit.

The exceptions are the index folder – for it the url to the page will be http://example.com, as well as the _error folder – it stores the 404 and 500 error page, which does not have its own url.

In the index folder there is a file +Page.ts, which describes the main page.

Home page code

Home page code

No logic beyond the standard React logic is used here, so let's move on.

Consider the todo page. The folder besides +Page.ts also contains +data.ts and +config.ts.
+config contains settings similar to the root +config (vike, in principle, allows you to override all +hooks at deeper nesting levels). In this case, it contains the prerender=false flag, which, generally speaking, makes no sense, because This is the default setting. It will be much more interesting to consider the +data file

+data.ts

+data.ts

A hook designed to request data. Here we can do fetch and axios.get as much as we like. The main thing to remember is that when using ssr, the request occurs not on the client, but on the server, which means document and window will not be available.

After receiving the data in +data.ts, we can access this data on our page using the useData() hook

todo/+Page.ts

todo/+Page.ts

It's worth mentioning the pageContext parameter. It starts from the pageContextInit file vike-handler. There we can transfer in advance all the parameters we need, including the results of requests to the API (but it’s better not to do this). Next, this context is passed to all the required nodes, right down to the react components themselves. The data content is actually part of the pageContext, but has a more convenient way to access it. Most often, pageContext is interesting to us because it contains urlPathName, urlParsed.search (access to query parameters), is404 and some other fields. It is also worth noting that pageContext differs on the server side and on the client side – some information is not transferred to the client to avoid leaks. However, this can be adjusted using the +config setting – passToClient:

Resolving Context Parameters

Resolving Context Parameters

In this example, we are telling vike that we want to have access to the user and is404 fields on the client side.

By the way, the user parameter is not standard for vike; in the example it is a custom field. To define your property inside pageContext, you need to pass it to pageContextInit, and also determine the type of the passed field in vike-pageContext.d.ts

vike-pageContext.d.ts

vike-pageContext.d.ts

Now we can access the user field in our components like this:
const {user} = usePageContext()

Let's continue to look at the file system. Let's pay attention to the star-wars folder. It contains two subfolders – index and @id. The first is a page with the url http://example/star-wars, but the second is a page with the id parameter. This means that this page matches any path like http://example/star-wars/@id , where @id is any substring. Similar constructions with path parameters are needed, for example, for individual pages of blog posts. We can access @id in the code through pageContext.routeParams – an object containing all path parameters. There can be many of them, for example http://example/star-wars/@id/@variant/@anyParam contains 3 parameters at once.

It’s also worth mentioning “globes” – that’s what the Yandex translator calls them on the documentation page. We can set a path like http://example/star-wars/* – which will mean any url starting with http://example/star-wars/. We can access all the content afterwards via pageContext.routeParams[‘*’]. However, situations where globes are needed are very rare.

The question may arise – how to describe the file structure in such a way as to add a globe? The answer is no way, because just an asterisk cannot be the name of a folder. But, we smoothly approached the possibilities that do not appear in the template being parsed – the +route hook

+route a hook is needed for cases when we want to set a complex path to a page without resorting to manipulations with the file structure, or there is non-standard logic in constructing the path. +route comes in two types:

  1. A regular string, for example export default 'blog/posts/@id/@variant/*'

  2. A quasi-predicate function that returns either false or a routeParams object. The route function should be simple, because… it is executed every time routing occurs. Before moving between pages, vike collects all existing paths, and also performs all route functions in an attempt to identify a match to the specified url. If a return false occurs during function execution, vike considers that the url does not correspond to this route.

Example of a routing function from the documentation

Example of a routing function from the documentation

There is also a hook for routing restrictions +guardthe meaning of which is simple – to prevent unauthorized access to the page.

Example of the guard function from the documentation

Example of the guard function from the documentation

I generally only perform user verification here, and although this hook does not prohibit the use of API requests, if they are necessary, it is usually better to do them in +data, so that it is possible to get the necessary information through useData in the future.
Moreover, all the logic of this hook can be moved inside +data, but for a better understanding of the code it is worth checking here if possible.

You can pay attention to the throw render() construct. This approach allows you to replace the drawn page with another one without changing the url during the rendering procedure (on the server only). This works well for 404 and 500 error pages. You can also use a path string to another page as an argument instead of a status number. In this way, I sometimes draw pages that depict the absence of content for some reason (the user does not have access to the page, the list of user posts is empty because the user has not created posts yet, etc.)

There is another option for moving between pages at the rendering stage – throw redirect() – does basically the same thing, but changes the url to the one specified in the argument. Suitable for cases when during the rendering process we need, for example, to send the user to the login page.

The last way to programmatically transfer the user to another page is navigate() – but this function is needed only on the client, and it does a simple thing – redirects the user to the specified url.

Hooks worth considering +Head, +title And +description.

The +Head file must contain a component that describes the contents of the tag. If necessary, we can redefine this component at more nested levels.
+title and +description create corresponding tags inside the with the content described within them. This can be either a string or a function from pageContext that returns a string. You can generate a title and description based on the data received in +data, for example, for a page with product cards for a specific category.

Now we come to the most interesting part – +Layout. From the name it becomes clear that this is something like a parent component, a hoc, applied to several pages at once. In the product I am developing, the header, footer pages, as well as some additional shells are described here. In my opinion, +Layout is going through hard times. The fact is that back in version 0.4.171 layouts could be overridden at lower nesting levels. This made it possible to set 1 global layout, but if necessary, remove it on other pages. In later versions, layouts were made inheritable, completely removing the ability to override the parent one and killing backward compatibility. I hope my comments in the relevant issue can lead to results)

Total

Above, I tried to reveal the main provisions of vike, which are sufficient for 90% of all tasks. You can read more about the framework in their documentation. Write your questions, I will definitely answer! If there are enough questions, I will publish an article where I will try to reveal in detail the aspects that interest you 🙂

Read also my colleague:

The saga of implementing DDD on Fastify in two parts
The Interpreter pattern: what it is and how to use it

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *