Nitropage v0.68 is out!

Smoothing the foundation

Katja Lutz

Web Developer

Announcements
21 July, 2025

I'm very excited to announce the release of v0.68! It has been quite some time since the last blog post, atleast for me, unfortunately not a great time... My incurable disease (phantosmia) or lets call it what it is, my disability, has become even worse this year and constantly forces me to stay in my bed, for hours or whole days. Nitropage has been one of my few motivations in these burdensome times and helped me stand up again and again, after hefty phantosmia seizures. If you are suffering from something that takes away your purpose in life, there is nothing better than working on something that gives it back!

But you are probably here to find out more about v0.68, so lets take a step back from philosophy and instead talk about the release! With a whooping 62 entries in the Changelog, this is definitely a big one. My focus has been to improve existing functionality and smooth out rough edges. But who would've guessed it, while working on it, I somehow kept finding more 😅. Further below you can find an overview of the most noteworthy highlights.

In parallel, over the past months, I searched for new installation options, for users without terminal experience and discovered Coolify and Railway. I am very happy to finally share these two options with you and am looking forward to see more Nitropage installations in the wild! You can learn more about Coolify and Railway in the Getting started guide.

Nitropage would not exist without the support, donations and great feedback from my family, friends and the community. Thank you wholeheartedly, to everyone who makes this project possible!

I'd like to invite you to share your experience with the new version or installation methods, over the freshly created feedback form. Stay updated by joining the newsletter or following on Nostr, Bluesky or Twitter.

Highlights of this release:


Keyboard shortcuts & Cancellable drag and drop

If anything, the existing keyboard shortcuts for saving and publishing have shown that there is an urgent need for further shortcuts. Lot's of common actions can now be triggered with handy shortcuts!

One action I'd like to mention here in particular: Dragging an element on the page and in the sidebar can be cancelled by pressing the ESC key. Started dragging the wrong element? Finally you can get out of that nasty situation 🥳.

Overhauled toolbar, save indicator & warnings

The tools in the toolbar have been rearranged to the right side. This puts them closer to the sidebar and reduces how far you have to move your cursor between the two.

Having no active tool is now indicated with the new Preview button. It shows the page as closely like the final result, as possible. This preview functionality already existed in previous versions, but was not communicated to the user well. The new button should make it more obvious.

If you have unsaved modifications or if the current revision isn't published yet, the Save and Publish buttons in the sidebar will show appropriate indicators, in v0.68. Furthermore if you try to leave the editor while having unsaved modifications, the editor now presents you with a dialog to save or discard your work.

Lastly the design of the buttons in the sidebar header has been updated to follow the shape of the Nitropage logo.

Blueprint categories

Finding the right blueprint during the creation of a new element has started to become difficult with the growing list of blueprints to choose from. v0.68 introduces filterable categories, helping you to find that blueprint you were looking for.

Fine-grained saving

The page saving/publishing story has been a though one in the past. Previously when you pressed on those save and publish buttons, it always overwrote everything, even the settings you didn't touch. While this might be acceptable for small websites maintained by single users, it meant that you could not work on multiple pages in parallel via browser tabs, as the work from one tab did overwrite your work from another one. It also meant that saving felt relatively slow in some situations.

v0.68 introduces a completely reworked saving and publishing experience as it only overwrites settings and configurations that you actually modified. This specifically applies to:

  • Colors
  • Fonts
  • Presets
  • Project blueprint defaults
  • Project settings

Changes directly made on the page elements themselves now always result in the creation of a new page revision. Keep in mind: If two users work on the same page, at the same time, their work will not be merged, instead the editor creates separate revisions for each user.

The creation of new page revisions has also received some optimizations and tests on a page with ~100 elements have shown speed improvements of 4-6x!

Revisions clean up tool

Now that saving the page always creates new revisions, this quickly can result in countless redundant revisions over time. To help you clean up unnecessary revisions, the editor received a cleanup dialog, that can easily delete revisions in bulk.

Updated dashboard design & count badges

The dashboard has stayed mostly untouched over the past versions and I wanted to change that with this release. So the header area now has a silky gradient with a soft touch of color. Plus the tabs are sticky, staying in view when you scroll down a large list of pages.

With the introduction of the Published column in the Pages and Layouts tabs, I finally saw the need to also show column titles.

Throughout the dashboard and also in the editor, you can now find badges showing the count of the respective items (e.g. pages, layouts, elements, etc.).

Pretty clean, isn't it 😎?

Reworked editor state synchronization

Keeping the viewport and the sidebar in sync, is harder than you might imagine. Both are two separate applications, running in parallel, with relatively independent lifecycles. Without any form of synchronization, if you were to change the text of some element in the viewport, this change would not be reflected in the sidebar. Understandably a smooth synchronization of the two is a crucial aspect of the whole editing experience.

In v0.68 this synchronization has been completely reworked. The new approach reduces overall lag and results in the editing experience remaining smooth as the page is growing with more and more elements. The viewport now also is able to skip rerendering elements in many cases, e.g. after creating new revisions. Plus, all used layouts will be reloaded after you save your changes. This really deserves a quick demonstration:

If you do not have a technical background you can stop reading here without worry, but I'd like to use the last part of this article to explain how the new synchronization actually works, on a deeper level.

The editor viewport is rendered in an iframe, reason being because content in the iframe is shown responsively according to the size of the iframe, allowing you to preview how a page will e.g. look on mobile, without having to resize your whole browser window. But this iframe comes at a huge cost:

It has a completely separate realm and is running an own Solid instance. Objects created in and out of the iframe are not the same, thus instanceof-like operations do not work properly, if you transfer objects between the realms. In previous Nitropage versions, the application state has been kept in sync between the viewport and sidebar, by serializing the whole state of the page, sending it back and forth and reconciling it vice versa. That has been a very expensive operation, slowing down more and more relative to the size of the state (page). This circumstance largely negated the advantage of Solid's fine grained update architecture, so I have been searching for a better approach since quite some time.

Rethinking synchronization

When I heard about Ryan Carniato's plan to get rid of cross-realm incompatible checks in Solid v2, I knew that my time has come to make Nitropage as smooth as it really should be as a Solid app.

The first step was to backport the deletion of the cross-realm incompatible checks with a relatively straightforward Vite plugin. With that settled, the door was finally opened to rethink the way the state is synchronized between the realms.

My idea: What if state operations could just run twice, once in the sidebar and another time in the viewport, no serialization or reconcile of the whole state necessary?! With this approach, the performance of the synchronization is no longer tied to the size of the state, but to the implementation of the mutation. As long as the mutation is relatively simple and touches just a few signals in the state, this is a huge win. If you need to replace the whole state, e.g. in Nitropage when the user restores an old page revision, then the old appoach via serialization and reconcile is still perfectly sensible.

Like everything in the life of an IT nerd person, the new approach does come with a tradeoff, a tradeoff in my opinion worth taking, but nonetheless a serious tradeoff:

Can you tell me the result of the following code, assuming the new approach?

setState(produce((draft) => {
  draft.id = Math.random();
}));

As the mutation runs twice, Math.random() also will run twice and you'll end up with different numbers in the sidebar and the viewport. Thus with the new approach, all state mutating code must be "pure":

const nextId = Math.random();
setState(produce((draft) => {
  draft.id = nextId;
}));

It also means that you have to use structuredClone (or an equivalent), when assigning objects from outside the mutation:

const nextProduct = { name: "Lincoln Six Echo" }
setState(produce((draft) => {
  draft.warehouse[0] = structuredClone(nextProduct);
}));

Not cloning the object would result in broken reactivity, as the object can only be reactive in one store. Basically you do not want, to let your objects run loose in realms they do not belong 😉. Or to quote one of my favorite action movies about clones:

"Yeah, I was wondering if you could get someone who can explain to me why my insurance policy is sitting downstairs on my fucking sofa!"

Reconciliation and the pain of changing ids

As mentioned earlier, some operations still are synchronized using the old approach using serialization and reconcile. Putting it simply, reconcile merges the new state into the old one, and tries to identify related objects by key or by field (usually id). Well... what if I tell you that in Nitropage element database id's are unique per page revision, so when you change the selected page revision in the editor, all elements will have different id's. Under normal circumstances this would lead to a rerender of the complete page, and in previous versions it indeed did.

Over the lifetime of Nitropage I experimented with many ways to solve this issue, but I think I have finally found one that does not introduce a lot of complex back-and-forth id-mapping and is good enough for the time being. The trick: Helping Solid's reconcile to identify the related elements in an extra iteration, before reconcile. Consider these states:

// Current state:
{
  1: { id: 1, name: "Chuck Norris" }
}

// Next state:
{
  2: { id: 2, name: "Still Chuck Norris", oldId: 1 }
}

Solid's reconcile has no way to know that both records are related, but you can help it out, by doing something like this:

setState(produce(draft => {
  for(const item of Object.values(nextState)) {
    draft[item.id] = draft[item.oldId];
  }
}))
reconcile(...)

Simpler than having to map id's back and forth all over the app, isn't it? If you want to see how this is actually implemented in Nitropage, you can find it here: Source code


Previous article

Announcements
19.08.2024

Nitropage v0.59

One small step for semver, one giant leap for website editing. Introducing SolidStart v1, S3 file storage, Markdown support, focal point image cropping, editor zoom, page load optimizations and a lot more.

READ MORE
Katja Lutz

Web Developer