Skip to content

Create account17032

Cover image for Building custom plugins for Vite
Jakub Andrzejewski

Jakub Andrzejewski

Posted on Oct 31, 2022


Building custom plugins for Vite


JavaScript (4 Part Series)

1Writing local rules for ESLint2Building custom plugins for Vite3Building universal JS/TS plugins with unplugin4Better DX in JS apps with unplugin-auto-import

I recently started building an Open Source project that is powered by Vite ⚡️ and I am amazed about the ecosystem of plugins it has. The Vite itself is a great tool that is changing how currently the modern web development looks and feels like. When I first tried it after working with Webpack (and 50-60 seconds project reload time) I immediately felt in love with it.

In this article, I will guide you through the process of building a simple custom plugin that will be responsible for setting certain cache-control headers. In this article, I won’t be publishing it in npm nor creating a separate repostiory for it, while instead I will be building it locally in vite.config.ts.

What is Vite?

If you are not yet familiar with Vite, it is Next Generation Frontend Tooling that’s really fast.


It is a build tool that aims to provide a faster and leaner development experience for modern web projects. It consists of two major parts:

A dev server that provides rich feature enhancements over native ES modules, for example extremely fast Hot Module Replacement (HMR).

A build command that bundles your code with Rollup, pre-configured to output highly optimized static assets for production.

Vite is opinionated and comes with sensible defaults out of the box, but is also highly extensible via its Plugin API and JavaScript API with full typing support.

What is great about Vite is that it works with any modern frontend framework like Vue, React, Svelte, Solid and more.

Vite Plugins?

When building a project with Vite, you can use plugins from it ecosystem that is really huge. You can check out the Awesome Vite list here for reference. In my personal project I am using like 10 plugins already 😀

Awesome Vite

The plugins allow for several different functionality like PWA, SSG, SSR, I18N, and many more.

Custom plugin code

As I mentioned in the beginning of the article, I will show you some code of the local plugin that I have recently created for my project that I decided could be quite useful to you as well.

The first thing you need to add a custom plugin to your Vite project is the vite.config.ts file. The configuration contains the plugins array that will accept a plugin object with certain configuration:

import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    // your plugins

In here, you will be registering your plugins like PWA, Vue, etc.

Right now, I will paste some code and explain it step by step:

import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
      name: 'vite-plugin-cache-control',
      configureServer(server: ViteDevServer) {
        return () => {
          server.middlewares.use(async (request, response, next) => {
            response.setHeader("Cache-Control", 'max-age=604800, stale-while-revalidate=86400');

  1. name – this is an unique name for your plugin. Nothing more to say basically, just make it unique 😉
  2. The logic of the plugin, in my case configureServer method. I needed to set certain header on the server response so I have added a new middleware that sets this header accordingly. Of course, this is not the full code of the plugin. I decided to skip things that are not necessary for this article.

And that’s it. This will make your Vite app to send this header with coresponding value to the browser.


Well done! You have just created a custom local plugin for your Vite project. Make sure to check out the documentation of Vite on how to build plugins and what other plugins are available 🙂

JavaScript (4 Part Series)

1Writing local rules for ESLint2Building custom plugins for Vite3Building universal JS/TS plugins with unplugin4Better DX in JS apps with unplugin-auto-import

Top comments (0)



Code of Conduct • Report abuse

DEV Community

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await

Read next

Upgrade text-davinci-003 to gpt-3.5-turboLuckey – 7 Essential React Libraries for Front-End Web DevelopmentSuraj Vishwakarma – Django + Unpoly = ️Alexandre Jacques – Install Drupal 10 & Tailwind CSSRussell Jones – 

Jakub AndrzejewskiFollow

• Senior Developer & Advocate @VueStorefront • @GoogleDevExpert in Web Performance • Ambassador @nuxt_js, @Storyblok, @algolia • Teaching Vue | Nuxt | Perf

  • LOCATIONWrocław, Poland
  • WORKSenior Developer & Dev Advocate Vue Storefront
  • JOINEDJun 20, 2021

More from Jakub Andrzejewski

Nuxt 3, Algolia, Storyblok, and TailwindCSS Crash Course

#vue #javascript #webdev #tutorialNuxt, Medusa, TailwindCSS Crash Course#vue #javascript #tailwindcss #webdevNuxt 3, Tailwind, and Supabase Crash Course#vue #tailwindcss #javascript #webdev

DEV Community

👋 Hey, my name is Noah and I’m the one who set up this ad. My job is to get you to join DEV, so if you fancy doing me a favor, I’d love for you to create an account.

If you found DEV from searching around, here are a couple of our most popular articles on DEV:

ForemOpen with the Forem appDismiss banner

DEV Community — A constructive and inclusive social network for software developers. With you every step of your journey.

Built on Forem — the open source software that powers DEV and other inclusive communities.

Made with love and Ruby on Rails. DEV Community © 2016 – 2023.

Skip to content

Get started


Content is Data: Announcing Sanity Content Lake

Learn about Content Lake, our real-time database for structured content.

Portrait of Simen Svale

Simen Svale

Co-founder and CTO at

Announcing: Sanity Content Lake

Today, we’re excited to announce the release of Content Lake, a real-time database that gives you access to your content however and wherever you need it. It is the culmination of months of engineering work, and years of iterating on a vision, that puts in place a key piece of our long-term strategy to become a unified content platform. Read on to learn more about Content Lake and the GROQ features that are included in this release.

Why we built Content Lake

We’ve been told by countless customers that they view Sanity as being the only content platform that covers their needs. We designed Sanity to free organizations from the trappings of typical content management systems (CMS) that force you to think of your content in the context of web pages and content hierarchies. Most headless CMSes have the same limitations but offer some API as if that would address the fundamental flaw of their approach. Although we hate being compared to a headless CMS, the framing is helpful when understanding why we built the Content Lake. When one examines the two components that make up a headless CMS, you have an authoring layer and a database. This post will focus on our enhancements to the database, but suffice to say that we’ve had a powerful authoring layer for quite some time.

The reason the database is so important to Sanity is due to our structured content foundation. Most platforms in our space treat content as a soup of information that is limited and defined by the format it was authored in. Sanity spearheaded a new approach to content and turned the old model on its head. Instead of thinking of the world in terms of web pages, structured content frees you to think of content as data – well-formed records that let you reshape and present your content in any format. But structured content alone doesn’t get modern organizations to where we think they need to be. A place where they can create compelling digital experiences that resonate with their audiences.

You also need a robust, open-source query language that lets you interact with your content. You need support for real-time editing and patching of your content so you can collaborate with humans (and bots!) without locking others out or accidentally overwriting their changes. You need developer tooling that feels familiar the moment you interact with it. You need the ability to transform and shape your content on the fly. You need to base your content delivery on best practices like Portable Text. Our motivation to build this ideal database comes down to a desire to move the entire industry forward. It’s what we think our customers need, and in the end, we felt we had to build it ourselves to ensure it was done properly.

We believe today’s announcement fundamentally transforms how the industry can interact with content today and in the future.

Getting it right for the launch

When we launched in 2017, our APIs were already real-time, patch-based, and had full revision history down to the keystroke. We knew it’d be almost impossible to retrofit it with this functionality later, so we took our time to get it right from the start. In addition, we are very, VERY serious about having APIs that don’t break. This perspective means our team needs to keep APIs, even those with inconsistent behavior, active as customers could be inadvertently relying on those inconsistencies as part of their content management workflows. This makes optimization hard and impractical. And this is a reason we have left our APIs mostly untouched since our launch several years ago.

Enter Content Lake. The Content Lake is the database that we’ve rebuilt from scratch over the past 10 months. We’ve reimplemented parsers and query planners to create a faster, more consistent solution that is easier for us to optimize. Starting today, the Content Lake is available to all Sanity customers.

API versioning

As mentioned we really don’t like to make API breaking changes. That’s why we are introducing API versioning to ensure that even as we fix bugs and release new features, customers relying on our platform can offer the same great experiences to their users while they decide how and when to migrate to the new versions of our APIs.

Our API versioning approach is pretty straightforward. Like the convention followed by Stripe, all Sanity endpoints are now versioned on ISO-dates:

The following query URL uses API version 2021-03-25:*

It’s worth mentioning that there is also an experimental endpoint vX that gives you early access to beta features.

Update your existing client configurations to reference the new APIs (more information can be found in our documentation) and get access to new features. If you do nothing, you’ll just continue to get the current version (v1).

New functions for GROQ!

A summary of the new features and functionality we are releasing as part of this Content Lake announcement is below. Remember, to get access, you’ll need to update your clients to reference the new versioned API endpoints.


This release brings function namespaces to the Sanity APIs. In addition to the default global namespace, this release introduces two new namespaces to GROQ functions: pt for portable text functionality and geo for geospatial queries.

Boosting, scoring, and plain-text

Need to weigh the return from a search query? Now you can. Give emphasis to matches on certain fields or mentions of strategic initiatives.

  • score() – takes an arbitrary number of valid GROQ expressions and returns a numerical score, which can be used to sort and filter items in an array.
  • boost() – create a sense of weight in a scoring algorithm. It accepts two arguments: an expression and the amount to boost the score if the expression returns true.

In addition, if you want to use boosting and scoring along with Portable Text you can now match against

  • pt::text() – a GROQ filter that returns a plain-text version of a Portable Text-field

Geospatial search

  • geo(object) – accepts an object as a parameter and, if possible, coerces the value to a geo-type document by a set of rules.
  • geo::latLng(latFloat, longFloat) – creating a new geo object for a singular point. Returns a geo object from the latitude and longitude floats provided.
  • geo::distance(geo-point, geo-point) – takes points and returns a numeric value for the distance between in meters.
  • geo::contains(geo-value, geo-value) – returns true when the first geographic geography value fully contains the geographic geometry value.
  • geo::intersects(geo-value, geo-value) – returns true when the two areas overlap or intersect.

Demo: The Community Map

To demo these new features, we put together a community map that lets you look for profiles by location, strings in their description, and technologies they have referred to.

Check out the map and the code.The Community Map

We’re thrilled, perhaps even stoked, to be releasing the Content Lake and are looking forward to the creative digital experiences these new features will empower you to build.


Structured ContentDeveloper experienceContent LakeSanity StudioSecurity & ComplianceCompare Sanity 



Resource libraryPartners Templates 

Case StudiesExplainers CMS 


Contact SalesEnterpriseCareersTerms of ServiceAccessibility Statement

Stay connected

©Sanity 2023Cookie preferences

Content is Data: Announcing Sanity Content Lake

The Community Map


Allow access to your location to filter by locality

Allow access



  // every person
  _type == "person" &&
  // that isn't hidden
  hidden != true &&
  defined(handle.current) &&
  !(_id in path("drafts.**")) &&
  // Has a photo & geolocation defined
  defined(geolocation) &&




Draw a polygonDraw a rectangle

Leaflet | © OpenStreetMap contributors

Made with SanitySanity UINextJS, and Leaflet.

%d bloggers like this: