Developing a design project is a unique and creative process, a symbiosis of the work of the customers and designers in London. Implementing a design project carried out to order in the interior design studio depends on many nuances: the number of changes made during the work, the speed of response to customer changes, and the availability of finishing materials in stock. Studio specialists usually take two to three months to develop a design project. The advantage of working with a creative interior design studio is that you can order the development of your design project according to your own unique needs and, if necessary, make changes during the execution of the design project. Thus, you will be able to organize your time as rationally as possible and calmly watch how an experienced team creates a unique interior solution in their studio. Wherever you are, studio managers are always ready to help and solve any problems related to contact designers. They strive to coordinate the work related to the project, which provides the opportunity to achieve a positive result in the shortest possible time. When designing the interior of another room, apartment, country house, or commercial building, only professionals can harmoniously organize the space between functionality, aesthetics, and ergonomics, so it is worth using the services of qualified designers with considerable experience in this area. Experienced specialists know all the tricks and nuances of working with light, textures, and textiles so that you can get the best from every design solution. A designer is someone who knows everything about fashion trends in interior design and tries to adapt them as best as possible to the individuality and taste of clients, thus bringing originality to their design projects.
What are the prices for interior design?
Creating a development project plan is free and requires nothing. It is essential to meet and decide the process of creating an interior following your taste, preferences, and wishes. The purpose of the first meeting is to get to know the client, determine the room’s dimensions, create a general concept of the planned design, and select all the nuances and characteristics of the given space. The finished room fully corresponds to the conceived idea for the project implementation to bring aesthetic satisfaction. It is essential to decide on the tastes and wishes of the client. The first visit to the object is necessary because this visit allows the team to immediately see the next steps for the successful implementation of the project conceived by the client. Then the development of several critical options for interior design takes place. If you like the plan, a contract is signed for the execution of the assigned works. An author’s solution is a service of a design studio that helps to achieve the desired results after the implementation of an interior design project. Using this option ensures the compatibility of the out-of-the-box visualization solution and saves valuable time.
It is creating an interior design that inspires admiration!
An impressive combination of comfort and elegance, details and unchanging style, aesthetics, and functionality are the main requirements that a team of qualified designers fulfills when creating an interior design. Find out how you live, your plans for the future, why you need repairs, and what tasks you entrust to designers to make the most original interior design. This is the only way to set priorities and create a solution that inspires you to new achievements. London is a city where dreams come true. The interior design of London flats should be appropriate: entirely in line with the taste and preferences, habits, taste, temperament, character, and lifestyle of the owner. Before starting work on the project, it is essential to know the likes and wishes of the client. The style can be coordinated with the designers. Technical tasks also require planning engineering and technical solutions in the project. These include underfloor heating, ventilation, or air conditioning.
Stages of work on an interior design project
Thanks to the team of experienced designers, the work processes are transparent and controlled. The contractor creates an action plan, which the client manages. He requests a schedule of work stages and decisions made by designers in the design project. As a rule, the design functions corresponding to the design project are assigned by the customer – in the author supervision process, the designer initiates the project. Following the developed draft project, the contractors (according to their forecasts) estimated their work performance. This is, for example, the manufacture and installation of stretch ceilings, air conditioning and ventilation systems, home theaters, doors, stairs, and walls. The design studio develops unique interior items to order.
I used to have this boss who loved, loved, loved, loved to emphasize words. This was way back before we used a WYSIWYG editors and I’d have to handcode that crap.
<p>
I used to have this boss who <em>loved</em>, <strong>loved</strong>,
<strong><em>loved</em></strong>, <strong><em><u>loved</u></em></strong>
to emphasize words.
</p>
(Let’s not go into the colors he used for even MOAR emphasis.)
Writing all that markup never felt great. The effort it took, sure, whatever. But is it even a good idea to add overload content with double — or more! — emphases?
Different tags convey different emphasis
For starters, the <strong> and <em> tags are designed for different uses. We got them back in HTML5, where:
So, <strong> gives the content more weight in the sense it suggests that the content in it is important or urgent. Think of a warning:
Warning: The following content has been flagged for being awesome.
It might be tempting to reach for <em> to do the same thing. Italicized text can be attention-grabbing after all. But it’s really meant as a hint to use more emphasis when readingt the content in it. For example, here are two versions of the same sentence with the emphasis in different locations:
<p>I ate the <em>entire</em> plate of burritos.</p>
<p>I ate the entire <em>plate</em> of burritos.</p>
Both examples stress emphasis, but on different words. And they would sound different if you were to read them out loud. That makes <em> a great way to express tone in your writing. It changes the meaning of the sentence in a way that <strong> does not.
Visual emphasis vs. semantic emphasis
Those are two things you gotta weigh when emphasizing content. Like, there are plenty of instances where you may need to italicize content without affecting the meaning of the sentence. But those can be handled with other tags that render italics:
<i>: This is the classic one! Before HTML5, this was used to stress emphasis with italics all over the place. Now, it’s purely used to italicize content visually without changing the semantic meaning.
<cite>: Indicating the source of a fact or figure. (“Source: CSS-Tricks“)
<address>: Used to mark up contact information, not only physical addresses, but things like email addresses and phone numbers too. (
howdy@example.com
)
It’s going to he the same thing with <strong>. Rather than using it for styling text you want to look heavier, it’s a better idea to use the classic <b> tag for boldfacing to avoid giving extra signficance to content that doesn’t need it. And remember, some elements like headings are already rendered in bold, thanks to the browser’s default styles. There’s no need to add even more strong emphasis.
Using italics in emphasized content (and vice versa)
There are legitimate cases where you may need to italicize part of a line that’s already emphasized. Or maybe add emphasis to a bit of text that’s already italicized.
A blockquote might be a good example. I’ve seen plenty of times where they are italicized for style, even though default browser styles don’t do it:
blockquote {
font-style: italic;
}
What if we need to mention a movie title in that blockquote? That should be italicized. There’s no stress emphasis needed, so an <i> tag will do. But it’s still weird to italicize something when it’s already rendered that way:
<blockquote>
This movie’s opening weekend performance offers some insight in
to its box office momentum as it fights to justify its enormous
budget. In its first weekend, <i>Avatar: The Way of Water</i> made
$134 million in North America alone and $435 million globally.
</blockquote>
In a situation where we’re italicizing something within italicized content like this, we’re supposed to remove the italics from the nested element… <i> in this case.
This little snippet evaluates the blockquote to see if it’s font-style is set to italic. If it is, then it’ll make sure the <em>, <i>, <cite>, and <address> elements are rendered as normal text, while retaining the semantic meaning if there is one.
But back to emphasis within emphasis
I wouldn’t nest <strong> inside <em> like this:
<p>I ate the <em><strong>entire</strong></em> plate of burritos.</p>
…or nest <em> inside <strong> instead:
<p>I ate the <em><strong>entire</strong></em> plate of burritos.</p>
The rendering is fine! And it doesn’t matter what order they’re in… at least in modern browsers. Jennifer Kyrnin mentions that some browsers only render the tag nearest to the text, but I didn’t bump into that anywhere in my limited tests. But something to watch for!
The reason I wouldn’t nest one form of emphasis in another is because it simply isn’t needed. There is no grammar rule that calls for it. Like exclamation points, one form of emphasis is enough, and you ought to use the one that matches what you’re after whether it’s visual, weight, or announced emphasis.
And even though some screen readers are capable of announcing emphasized content, they won’t read the markup with any additional importance or emphasis. So, no additional accessibility perks either, as far as I can tell.
But I really want all the emphasis!
If you’re in the position where your boss is like mine and wants ALL the emphasis, I’d reach for the right HTML tag for the type of emphasis, then apply the rest of the styles with a mix of tags that don’t affect semantics with CSS to help account for anything browser styles won’t handle.
<style>
/* If `em` contains `b` or `u` tags */
em:has(b, u) {
color: #f8a100;
}
</style>
<p>
I used to have this boss who <em>loved</em>, <strong>loved</strong>,
<strong><em>loved</em></strong>, <strong><em><u>loved</u></em></strong>
to emphasize words.
</p>
I might even do it with the <strong> tag too as a defensive measure:
/* If `em` contains `b` or `u` tags */
em:has(b, u),
/* If `strong` contains `em` or `u` tags */
strong:has(i, u) {
color: #f8a100;
}
As long as we’re playing defense, we can identify errors where emphases are nested within emphases by highlighting them in red or something:
Then I’d probably use that snippet from the last section that removes the default italic styling from an element when it is nested in another italiczed element.
Anything else?
Mayyyyybe:
Make sure your webfont includes bold and italic variations — otherwise, you’ll be relying on the browser to try to bold or italicize text for you. But limit your font files to just the weights and styles you need for better performance.
Check your analytics for the browsers your visitors use and test accordingly. Even though I didn’t run into a browser that balked at <strong> in <em> or the other way around, there may be a browser or several that will.
Do you know that kind of effect where someone’s head is poking through a circle or hole? The famous Porky Pig animation where he waves goodbye while popping out of a series of red rings is the perfect example, and Kilian Valkhof actually re-created that here on CSS-Tricks a while back.
I have a similar idea but tackled a different way and with a sprinkle of animation. I think it’s pretty practical and makes for a neat hover effect you can use on something like your own avatar.
CodePen Embed Fallback
See that? We’re going to make a scaling animation where the avatar seems to pop right out of the circle it’s in. Cool, right? Don’t look at the code and let’s build this animation together step-by-step.
The HTML: Just one element
If you haven’t checked the code of the demo and you are wondering how many divs this’ll take, then stop right there, because our markup is nothing but a single image element:
<img src="" alt="">
Yes, a single element! The challenging part of this exercise is using the smallest amount of code possible. If you have been following me for a while, you should be used to this. I try hard to find CSS solutions that can be achieved with the smallest, most maintainable code possible.
I wrote a series of articles here on CSS-Tricks where I explore different hover effects using the same HTML markup containing a single element. I go into detail on gradients, masking, clipping, outlines, and even layout techniques. I highly recommend checking those out because I will re-use many of the tricks in this post.
An image file that’s square with a transparent background will work best for what we’re doing. Here’s the one I’m using if you want start with that.
I’m hoping to see lots of examples of this as possible using real images — so please share your final result in the comments when you’re done so we can build a collection!
Before jumping into CSS, let’s first dissect the effect. The image gets bigger on hover, so we’ll for sure use transform: scale() in there. There’s a circle behind the avatar, and a radial gradient should do the trick. Finally, we need a way to create a border at the bottom of the circle that creates the appearance of the avatar behind the circle.
We said that the background would be a radial gradient. That’s perfect because we can create hard stops between the colors of a radial gradient, which make it look like we’re drawing a circle with solid lines.
Note the CSS variable, --b, I’m using there. It represents the thickness of the “border” which is really just being used to define the hard color stops for the red part of the radial gradient.
CodePen Embed Fallback
The next step is to play with the gradient size on hover. The circle needs to keep its size as the image grows. Since we are applying a scale() transformation, we actually need to decrease the size of the circle because it otherwise scales up with the avatar. So, while the image scales up, we need the gradient to scale down.
Let’s start by defining a CSS variable, --f, that defines the “scale factor”, and use it to set the size of the circle. I’m using 1 as the default value, as in that’s the initial scale for the image and the circle that we transform from.
Here is a demo to illustrate the trick. Hover to see what is happening behind the scenes:
CodePen Embed Fallback
I added a third color to the radial-gradient to better identify the area of the gradient on hover:
Now we have to position our background at the center of the circle and make sure it takes up the full height. I like to declare everything directly on the background shorthand property, so we can add our background positioning and make sure it doesn’t repeat by tacking on those values right after the radial-gradient():
The background is placed at the center (50%), has a width equal to calc(100%/var(--f)), and has a height equal to 100%.
Nothing scales when --f is equal to 1 — again, our initial scale. Meanwhile, the gradient takes up the full width of the container. When we increase --f, the element’s size grows — thanks to the scale() transform — and the gradient’s size decreases.
Here’s what we get when we apply all of this to our demo:
CodePen Embed Fallback
We’re getting closer! We have the overflow effect at the top, but we still need to hide the bottom part of the image, so it looks like it is popping out of the circle rather than sitting in front of it. That’s the tricky part of this whole thing and is what we’re going to do next.
The bottom border
I first tried tackling this with the border-bottom property, but I was unable to find a way to match the size of the border to the size to the circle. Here’s the best I could get and you can immediately see it’s wrong:
CodePen Embed Fallback
The actual solution is to use the outline property. Yes, outline, not border. In a previous article, I show how outline is powerful and allows us to create cool hover effects. Combined with outline-offset, we have exactly what we need for our effect.
The idea is to set an outline on the image and adjust its offset to create the bottom border. The offset will depend on the scaling factor the same way the gradient size did.
CodePen Embed Fallback
Now we have our bottom “border” (actually an outline) combined with the “border” created by the gradient to create a full circle. We still need to hide portions of the outline (from the top and the sides), which we’ll get to in a moment.
Here’s our code so far, including a couple more CSS variables you can use to configure the image size (--s) and the “border” color (--c):
Since we need a circular bottom border, we added a border-radius on the bottom side, allowing the outline to match the curvature of the gradient.
The calculation used on outline-offset is a lot more straightforward than it looks. By default, outline is drawn outside of the element’s box. And in our case, we need it to overlap the element. More precisely, we need it to follow the circle created by the gradient.
When we scale the element, we see the space between the circle and the edge. Let’s not forget that the idea is to keep the circle at the same size after the scale transformation runs, which leaves us with the space we will use to define the outline’s offset as illustrated in the above figure.
Let’s not forget that the second element is scaled, so our result is also scaled… which means we need to divide the result by f to get the real offset value:
Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2
We add a negative sign since we need the outline to go from the outside to the inside:
Offset = (1/f - 1) * S/2
Here’s a quick demo that shows how the outline follows the gradient:
CodePen Embed Fallback
You may already see it, but we still need the bottom outline to overlap the circle rather than letting it bleed through it. We can do that by removing the border’s size from the offset:
There is no particular logic to that top padding. The idea is to ensure the outline doesn’t touch the avatar’s head. I used the element’s size to define that space to always have the same proportion.
Note that I have added the content-box value to the background:
We need this because we added padding and we only want the background set to the content box, so we must explicitly tell the background to stop there.
Adding CSS mask to the mix
We reached the last part! All we need to do is to hide some pieces, and we are done. For this, we will rely on the mask property and, of course, gradients.
Here is a figure to illustrate what we need to hide or what we need to show to be more accurate
The left image is what we currently have, and the right is what we want. The green part illustrates the mask we must apply to the original image to get the final result.
We can identify two parts of our mask:
A circular part at the bottom that has the same dimension and curvature as the radial gradient we used to create the circle behind the avatar
A rectangle at the top that covers the area inside the outline. Notice how the outline is outside the green area at the top — that’s the most important part, as it allows the outline to be cut so that only the bottom part is visible.
Let’s break down that mask property. For starters, notice that a similar radial-gradient() from the background property is in there. I created a new variable, --_g, for the common parts to make things less cluttered.
This creates the rectangle part of the mask. Its width is equal to the radial gradient’s width minus twice the border thickness:
calc(100% / var(--f) - 2 * var(--b))
The rectangle’s height is equal to half, 50%, of the element’s size.
We also need the linear gradient placed at the horizontal center (50%) and offset from the top by the same value as the outline’s offset. I created another CSS variable, --_o, for the offset we previously defined:
One of the confusing things here is that we need a negative offset for the outline (to move it from outside to inside) but a positive offset for the gradient (to move from top to bottom). So, if you’re wondering why we multiply the offset, --_o, by -1, well, now you know!
Here is a demo to illustrate the mask’s gradient configuration:
CodePen Embed Fallback
Hover the above and see how everything move together. The middle box illustrates the mask layer composed of two gradients. Imagine it as the visible part of the left image, and you get the final result on the right!
Wrapping up
Oof, we’re done! And not only did we wind up with a slick hover animation, but we did it all with a single HTML <img> element. Just that and less than 20 lines of CSS trickery!
Sure, we relied on some little tricks and math formulas to reach such a complex effect. But we knew exactly what to do since we identified the pieces we needed up-front.
Could we have simplified the CSS if we allowed ourselves more HTML? Absolutely. But we’re here to learn new CSS tricks! This was a good exercise to explore CSS gradients, masking, the outline property’s behavior, transformations, and a whole bunch more. If you felt lost at any point, then definitely check out my series that uses the same general concepts. It sometimes helps to see more examples and use cases to drive a point home.
I will leave you with one last demo that uses photos of popular CSS developers. Don’t forget to show me a demo with your own image so I can add it to the collection!
My previous post was a broad overview of SvelteKit where we saw what a great tool it is for web development. This post will fork off what we did there and dive into every developer’s favorite topic: caching. So, be sure to give my last post a read if you haven’t already. The code for this post is available on GitHub, as well as a live demo.
This post is all about data handling. We’ll add some rudimentary search functionality that will modify the page’s query string (using built-in SvelteKit features), and re-trigger the page’s loader. But, rather than just re-query our (imaginary) database, we’ll add some caching so re-searching prior searches (or using the back button) will show previously retrieved data, quickly, from cache. We’ll look at how to control the length of time the cached data stays valid and, more importantly, how to manually invalidate all cached values. As icing on the cake, we’ll look at how we can manually update the data on the current screen, client-side, after a mutation, while still purging the cache.
This will be a longer, more difficult post than most of what I usually write since we’re covering harder topics. This post will essentially show you how to implement common features of popular data utilities like react-query; but instead of pulling in an external library, we’ll only be using the web platform and SvelteKit features.
Unfortunately, the web platform’s features are a bit lower level, so we’ll be doing a bit more work than you might be used to. The upside is we won’t need any external libraries, which will help keep bundle sizes nice and small. Please don’t use the approaches I’m going to show you unless you have a good reason to. Caching is easy to get wrong, and as you’ll see, there’s a bit of complexity that’ll result in your application code. Hopefully your data store is fast, and your UI is fine allowing SvelteKit to just always request the data it needs for any given page. If it is, leave it alone. Enjoy the simplicity. But this post will show you some tricks for when that stops being the case.
Speaking of react-query, it was just released for Svelte! So if you find yourself leaning on manual caching techniques a lot, be sure to check that project out, and see if it might help.
Setting up
Before we start, let’s make a few small changes to the code we had before. This will give us an excuse to see some other SvelteKit features and, more importantly, set us up for success.
First, let’s move our data loading from our loader in +page.server.js to an API route. We’ll create a +server.js file in routes/api/todos, and then add a GET function. This means we’ll now be able to fetch (using the default GET verb) to the /api/todos path. We’ll add the same data loading code as before.
import { json } from "@sveltejs/kit";
import { getTodos } from "$lib/data/todoData";
export async function GET({ url, setHeaders, request }) {
const search = url.searchParams.get("search") || "";
const todos = await getTodos(search);
return json(todos);
}
Next, let’s take the page loader we had, and simply rename the file from +page.server.js to +page.js (or .ts if you’ve scaffolded your project to use TypeScript). This changes our loader to be a “universal” loader rather than a server loader. The SvelteKit docs explain the difference, but a universal loader runs on both the server and also the client. One advantage for us is that the fetch call into our new endpoint will run right from our browser (after the initial load), using the browser’s native fetch function. We’ll add standard HTTP caching in a bit, but for now, all we’ll do is call the endpoint.
Yep, forms can target directly to our normal page loaders. Now we can add a search term in the search box, hit Enter, and a “search” term will be appended to the URL’s query string, which will re-run our loader and search our to-do items.
Let’s also increase the delay in our todoData.js file in /lib/data. This will make it easy to see when data are and are not cached as we work through this post.
We’ll look at manual invalidation shortly, but all this function says is to cache these API calls for 60 seconds. Set this to whatever you want, and depending on your use case, stale-while-revalidate might also be worth looking into.
And just like that, our queries are caching.
Note make sure you un-check the checkbox that disables caching in dev tools.
Remember, if your initial navigation on the app is the list page, those search results will be cached internally to SvelteKit, so don’t expect to see anything in DevTools when returning to that search.
What is cached, and where
Our very first, server-rendered load of our app (assuming we start at the /list page) will be fetched on the server. SvelteKit will serialize and send this data down to our client. What’s more, it will observe the Cache-Control header on the response, and will know to use this cached data for that endpoint call within the cache window (which we set to 60 seconds in put example).
After that initial load, when you start searching on the page, you should see network requests from your browser to the /api/todos list. As you search for things you’ve already searched for (within the last 60 seconds), the responses should load immediately since they’re cached.
What’s especially cool with this approach is that, since this is caching via the browser’s native caching, these calls could (depending on how you manage the cache busting we’ll be looking at) continue to cache even if you reload the page (unlike the initial server-side load, which always calls the endpoint fresh, even if it did it within the last 60 seconds).
Obviously data can change anytime, so we need a way to purge this cache manually, which we’ll look at next.
Cache invalidation
Right now, data will be cached for 60 seconds. No matter what, after a minute, fresh data will be pulled from our datastore. You might want a shorter or longer time period, but what happens if you mutate some data and want to clear your cache immediately so your next query will be up to date? We’ll solve this by adding a query-busting value to the URL we send to our new /todos endpoint.
Let’s store this cache busting value in a cookie. That value can be set on the server but still read on the client. Let’s look at some sample code.
We can create a +layout.server.js file at the very root of our routes folder. This will run on application startup, and is a perfect place to set an initial cookie value.
You may have noticed the isDataRequest value. Remember, layouts will re-run anytime client code calls invalidate(), or anytime we run a server action (assuming we don’t turn off default behavior). isDataRequest indicates those re-runs, and so we only set the cookie if that’s false; otherwise, we send along what’s already there.
The httpOnly: false flag is also significant. This allows our client code to read these cookie values in document.cookie. This would normally be a security concern, but in our case these are meaningless numbers that allow us to cache or cache bust.
Reading cache values
Our universal loader is what calls our /todos endpoint. This runs on the server or the client, and we need to read that cache value we just set up no matter where we are. It’s relatively easy if we’re on the server: we can call await parent() to get the data from parent layouts. But on the client, we’ll need to use some gross code to parse document.cookie:
export function getCookieLookup() {
if (typeof document !== "object") {
return {};
}
return document.cookie.split("; ").reduce((lookup, v) => {
const parts = v.split("=");
lookup[parts[0]] = parts[1];
return lookup;
}, {});
}
const getCurrentCookieValue = name => {
const cookies = getCookieLookup();
return cookies[name] ?? "";
};
Fortunately, we only need it once.
Sending out the cache value
But now we need to send this value to our /todos endpoint.
getCurrentCookieValue('todos-cache') has a check in it to see if we’re on the client (by checking the type of document), and returns nothing if we are, at which point we know we’re on the server. Then it uses the value from our layout.
Busting the cache
But how do we actually update that cache busting value when we need to? Since it’s stored in a cookie, we can call it like this from any server action:
It’s all downhill from here; we’ve done the hard work. We’ve covered the various web platform primitives we need, as well as where they go. Now let’s have some fun and write application code to tie it all together.
For reasons that’ll become clear in a bit, let’s start by adding an editing functionality to our /list page. We’ll add this second table row for each todo:
And, of course, we’ll need to add a form action for our /list page. Actions can only go in .server pages, so we’ll add a +page.server.js in our /list folder. (Yes, a +page.server.js file can co-exist next to a +page.js file.)
We’re grabbing the form data, forcing a delay, updating our todo, and then, most importantly, clearing our cache bust cookie.
Let’s give this a shot. Reload your page, then edit one of the to-do items. You should see the table value update after a moment. If you look in the Network tab in DevToold, you’ll see a fetch to the /todos endpoint, which returns your new data. Simple, and works by default.
Immediate updates
What if we want to avoid that fetch that happens after we update our to-do item, and instead, update the modified item right on the screen?
This isn’t just a matter of performance. If you search for “post” and then remove the word “post” from any of the to-do items in the list, they’ll vanish from the list after the edit since they’re no longer in that page’s search results. You could make the UX better with some tasteful animation for the exiting to-do, but let’s say we wanted to not re-run that page’s load function but still clear the cache and update the modified to-do so the user can see the edit. SvelteKit makes that possible — let’s see how!
First, let’s make one little change to our loader. Instead of returning our to-do items, let’s return a writeable store containing our to-dos.
return {
todos: writable(todos),
};
Before, we were accessing our to-dos on the data prop, which we do not own and cannot update. But Svelte lets us return our data in their own store (assuming we’re using a universal loader, which we are). We just need to make one more tweak to our /list page.
Instead of this:
{#each todos as t}
…we need to do this since todos is itself now a store.:
{#each $todos as t}
Now our data loads as before. But since todos is a writeable store, we can update it.
First, let’s provide a function to our use:enhance attribute:
This will run before a submit. Let’s write that next:
function executeSave({ data }) {
const id = data.get("id");
const title = data.get("title");
return async () => {
todos.update(list =>
list.map(todo => {
if (todo.id == id) {
return Object.assign({}, todo, { title });
} else {
return todo;
}
})
);
};
}
This function provides a data object with our form data. We return an async function that will run after our edit is done. The docs explain all of this, but by doing this, we shut off SvelteKit’s default form handling that would have re-run our loader. This is exactly what we want! (We could easily get that default behavior back, as the docs explain.)
We now call update on our todos array since it’s a store. And that’s that. After editing a to-do item, our changes show up immediately and our cache is cleared (as before, since we set a new cookie value in our editTodo form action). So, if we search and then navigate back to this page, we’ll get fresh data from our loader, which will correctly exclude any updated to-do items that were updated.
We can set cookies in any server load function (or server action), not just the root layout. So, if some data are only used underneath a single layout, or even a single page, you could set that cookie value there. Moreoever, if you’re not doing the trick I just showed manually updating on-screen data, and instead want your loader to re-run after a mutation, then you could always set a new cookie value right in that load function without any check against isDataRequest. It’ll set initially, and then anytime you run a server action that page layout will automatically invalidate and re-call your loader, re-setting the cache bust string before your universal loader is called.
Writing a reload function
Let’s wrap-up by building one last feature: a reload button. Let’s give users a button that will clear cache and then reload the current query.
In a real project you probably wouldn’t copy/paste the same code to set the same cookie in the same way in multiple places, but for this post we’ll optimize for simplicity and readability.
We could call this done and move on, but let’s improve this solution a bit. Specifically, let’s provide feedback on the page to tell the user the reload is happening. Also, by default, SvelteKit actions invalidate everything. Every layout, page, etc. in the current page’s hierarchy would reload. There might be some data that’s loaded once in the root layout that we don’t need to invalidate or re-load.
So, let’s focus things a bit, and only reload our to-dos when we call this function.
We’re setting a new reloading variable to true at the start of this action. And then, in order to override the default behavior of invalidating everything, we return an async function. This function will run when our server action is finished (which just sets a new cookie).
Without this async function returned, SvelteKit would invalidate everything. Since we’re providing this function, it will invalidate nothing, so it’s up to us to tell it what to reload. We do this with the invalidate function. We call it with a value of reload:todos. This function returns a promise, which resolves when the invalidation is complete, at which point we set reloading back to false.
Lastly, we need to sync our loader up with this new reload:todos invalidation value. We do that in our loader with the depends function:
export async function load({ fetch, url, setHeaders, depends }) {
depends('reload:todos');
// rest is the same
And that’s that. depends and invalidate are incredibly useful functions. What’s cool is that invalidate doesn’t just take arbitrary values we provide like we did. We can also provide a URL, which SvelteKit will track, and invalidate any loaders that depend on that URL. To that end, if you’re wondering whether we could skip the call to depends and invalidate our /api/todos endpoint altogether, you can, but you have to provide the exact URL, including the search term (and our cache value). So, you could either put together the URL for the current search, or match on the path name, like this:
invalidate(url => url.pathname == "/api/todos");
Personally, I find the solution that uses depends more explicit and simple. But see the docs for more info, of course, and decide for yourself.
If you’d like to see the reload button in action, the code for it is in this branch of the repo.
Parting thoughts
This was a long post, but hopefully not overwhelming. We dove into various ways we can cache data when using SvelteKit. Much of this was just a matter of using web platform primitives to add the correct cache, and cookie values, knowledge of which will serve you in web development in general, beyond just SvelteKit.
Moreover, this is something you absolutely do not need all the time. Arguably, you should only reach for these sort of advanced features when you actually need them. If your datastore is serving up data quickly and efficiently, and you’re not dealing with any kind of scaling problems, there’s no sense in bloating your application code with needless complexity doing the things we talked about here.
As always, write clear, clean, simple code, and optimize when necessary. The purpose of this post was to provide you those optimization tools for when you truly need them. I hope you enjoyed it!
But if you’re looking for gains on the CSS side of things, Patrick has a nice way of sniffing out your most expensive selectors using Edge DevTools:
Crack open DevTools.
Head to the Performance Tab.
Make sure you have the “Enable advanced rendering instrumentation” option enabled. This tripped me up in the process.
Record a page load.
Open up the “Bottom-Up” tab in the report.
Check out your the size of your recalculated styles.
From here, click on one of the Recalculated Style events in the Main waterfall view and you’ll get a new “Selector Stats” tab. Look at all that gooey goodness!
Now you see all of the selectors that were processed and they can be sorted by how long they took, how many times they matched, the number of matching attempts, and something called “fast reject count” which I learned is the number of elements that were easy and quick to eliminate from matching.
A lot of insights here if CSS is really the bottleneck that needs investigating. But read Patrick’s full post over on the Microsoft Edge Blog because he goes much deeper into the why’s and how’s, and walks through an entire case study.