Close Menu
ToolTechBlogToolTechBlog

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Bose QuietComfort Headphones (2nd gen) review: Upgraded in all the right places

    September 11, 2026

    Jensen Huang explains why Nvidia will grow an astounding 70% next year

    September 11, 2026

    A Developer’s Look at Integrating AI Speech Into Applications

    September 11, 2026
    Facebook X (Twitter) Instagram
    ToolTechBlogToolTechBlog
    • Home
    • AI Tools
    • Web Hosting
    • Tech
    • Digital Marketing
    • Business Software
    • VPN & Cybersecurity
    ToolTechBlogToolTechBlog
    Home»Web Hosting»Build a Vite Plugin to Catch Broken SVG Assets Before Production
    Web Hosting

    Build a Vite Plugin to Catch Broken SVG Assets Before Production

    Tool Tech TeamBy Tool Tech TeamAugust 25, 2026No Comments13 Mins Read
    Facebook Twitter Pinterest Telegram LinkedIn Tumblr WhatsApp Email
    Build a Vite Plugin to Catch Broken SVG Assets Before Production
    Share
    Facebook Twitter LinkedIn Pinterest Telegram Email

    Community Article
    Community articles are authored by SitePoint Premium contributors. Content is screened before publication, and SitePoint reserves the right to moderate or remove articles that violate our guidelines. Views expressed are those of the authors and do not necessarily reflect those of SitePoint.

    Build a Vite Plugin to Catch Broken SVG Assets Before Production

    SASaifullah AdenwallaPublished inPlugins·Canvas & SVG·
    August 25, 2026
    ·Updated:August 25, 2026

    The AI briefing for Developers

    Stay up to date with AI tools, model releases, and developer workflows that matter.

    Weekly. Free. One click to leave.

    SitePoint Premium
    Stay Relevant and Grow Your Career in Tech

    • Premium Results
    • Publish articles on SitePoint
    • Daily curated jobs
    • Learning Paths
    • Discounts to dev tools

    7 Day Free Trial. Cancel Anytime.

    SVG files have an unusual position in frontend projects.

    They look like ordinary static assets, but they’re also structured documents. They can contain paths, styles, IDs, embedded images, masks, filters, scripts, dimensions, and metadata. That flexibility is one of SVG’s strengths, but it also means a seemingly harmless asset change can introduce problems that aren’t discovered until much later.

    A designer replaces a logo, and suddenly dark mode stops working. An exported SVG has no view box, so responsive sizing behaves unexpectedly. Another export contains hundreds of kilobytes of unnecessary editor metadata. A third includes markup that your team’s asset policy doesn’t allow.

    None of these failures necessarily breaks the JavaScript build.

    Vite happily starts.

    The application renders.

    The pull request gets merged.

    The problem reaches production.

    Instead of relying on developers to manually inspect every SVG, we can teach the build system what a valid project asset looks like.

    In this tutorial, we’ll create a small custom Vite plugin that:

    scans SVG assets↓validates project rules↓reports useful errors↓watches files during development↓blocks invalid production builds

    The interesting part isn’t SVG itself. It’s how Vite’s plugin system lets us turn project-specific knowledge into developer tooling.

    If you’re relatively new to the build tool, Site Point’s introduction to Vite provides useful background on how Vite approaches development and production builds.

    The Problem with Validation After Deployment

    Suppose a project contains these files:

    src/└── assets/└── brand/├── logo.svg├── logo-inverse.svg└── mark.svg

    The frontend imports them normally:

    importlogoUrlfrom"./assets/brand/logo.svg";

    and renders the resulting asset:

    <imgsrc={logoUrl}alt="Acme"/>

    Everything looks straightforward.

    But imagine someone replaces logo.svg with this:

    <svgxmlns="http://www.w3.org/2000/svg"width="1200"height="400"><pathd="..."/></svg>

    The file contains no viewBox.

    That might not immediately be obvious during review.

    Or perhaps the new export is 750KB rather than 18KB because a design application included unnecessary information.

    Again, the build succeeds.

    What we really want is something closer to a type checker for assets.

    When the project’s expectations aren’t satisfied, the developer should see:

    SVG validation failed:src/assets/brand/logo.svg→ missing viewBox attribute

    before the change is shipped.

    Vite plugins are a good fit because they’re already part of the development and build lifecycle.

    Start With a Small Vite Project

    The plugin isn’t framework-specific, so you can use React, Vue, Svelte, or vanilla JavaScript.

    For simplicity, assume we already have a Vite application.

    Our project structure will eventually look like this:

    project/├── src/│   ├── assets/│   │   └── brand/│   │       ├── logo.svg│   │       └── mark.svg│   ││   └── main.js│├── plugins/│   └── svg-validator.js│├── vite.config.js└── package.json

    Instead of publishing a plugin package immediately, we’ll keep the plugin inside the application.

    That’s useful when developing project-specific tooling because we can establish whether the abstraction is actually useful before turning it into another dependency.

    plugins/svg-validator.js

    and start with the smallest possible plugin:

    exportfunctionsvgValidator(){return{name:"svg-validator",buildStart(){console.log("SVG validator started");}};}

    Then load it from vite.config.js:

    import{ defineConfig }from"vite";import{ svgValidator }from"./plugins/svg-validator.js";exportdefaultdefineConfig({plugins:[svgValidator()]});
    npm run dev

    and our plugin becomes part of the Vite lifecycle.

    This tiny example doesn’t accomplish much yet, but it establishes an important architectural boundary:

    Application↓Vite↓Our project-specific plugin

    We can now build validation into the same tool developers already use.

    SitePoint has explored Vite from both introductory and architectural perspectives, including a deeper look at Vite’s plugin system anderic build tool needs to understand rules that are specific to your project

    Finding the SVG Assets

    Let’s tell the plugin where our files live.

    We’ll use Node’s built-in file-system APIs rather than pulling in another dependency.

    import{readdir,readFile,stat}from"node:fs/promises";importpathfrom"node:path";

    Now create a recursive function:

    asyncfunctionfindSvgFiles(directory){const entries =awaitreaddir(directory,{withFileTypes:true});const files =[];for(const entry of entries){const fullPath = path.join(directory,entry.name);if(entry.isDirectory()){files.push(...awaitfindSvgFiles(fullPath));continue;}if(entry.isFile()&&entry.name.toLowerCase().endsWith(".svg")){files.push(fullPath);}}return files;}

    For this tutorial, we’ll validate everything under:

    src/assets/brand/

    The plugin can resolve that path when it starts:

    const assetDirectory = path.resolve("src/assets/brand");
    const files =awaitfindSvgFiles(assetDirectory);

    At this point, we’ve already moved beyond manually listing assets.

    product-logo.svg

    next week and it automatically becomes part of the validation process.

    That’s exactly what we want from project tooling.

    Define What “Valid” Means

    The plugin can’t determine whether a logo is visually attractive.

    It shouldn’t try.

    Instead, we’ll define deterministic requirements.

    For this project, let’s say every SVG must:

    contain an <svg> rootcontain a viewBoxstay under 100KBavoid <script>avoid <foreignObject>

    These aren’t universal SVG laws.

    They’re project policy.

    That’s a useful distinction.

    Build tools are excellent at answering:

    Does this file satisfy the contract we agreed on?

    Is this design good?

    Keep subjective decisions with designers and deterministic decisions with tooling.

    asyncfunctionvalidateSvg(filePath){const source =awaitreadFile(filePath,"utf8");const errors =[];if(!/<svg[s>]/i.test(source)){errors.push("missing <svg> root element");}if(!/bviewBoxs*=/i.test(source)){errors.push("missing viewBox attribute");}if(/<script[s>]/i.test(source)){errors.push("<script> elements are not allowed");}if(/<foreignObject[s>]/i.test(source)){errors.push("<foreignObject> is not allowed");}return errors;}

    Notice what this code is not doing.

    It’s not pretending that regular expressions are a complete XML parser or an SVG security sanitizer.

    We’re checking for a narrow set of patterns in files controlled by our project.

    If you’re handling arbitrary user-supplied SVG documents, you need a much more defensive sanitization strategy.

    That’s a separate problem from the repository-level developer tool we’re building here.

    The distinction is important because frontend teams sometimes accidentally use build-time validation techniques as though they were security boundaries.

    They’re not.

    Enforce a File-size Budget

    const fileStats =awaitstat(filePath);
    constMAX_SVG_SIZE=100*1024;

    That’s 100KB.

    if(fileStats.size>MAX_SVG_SIZE){errors.push(`file exceeds 100KB budget`);}

    Our complete function becomes:

    asyncfunctionvalidateSvg(filePath){const[source,fileStats]=awaitPromise.all([readFile(filePath,"utf8"),stat(filePath)]);const errors =[];if(!/<svg[s>]/i.test(source)){errors.push("missing <svg> root element");}if(!/bviewBoxs*=/i.test(source)){errors.push("missing viewBox attribute");}if(/<script[s>]/i.test(source)){errors.push("<script> elements are not allowed");}if(/<foreignObject[s>]/i.test(source)){errors.push("<foreignObject> is not allowed");}if(fileStats.size>MAX_SVG_SIZE){errors.push("file exceeds 100KB budget");}return errors;}

    A size budget is particularly useful for graphical assets because large regressions can otherwise be difficult to notice in code review.

    14KB
    684KB

    The visual difference may be negligible.

    The delivery cost isn’t.

    Rather than expecting the reviewer to manually inspect file sizes, the project can make the requirement executable.

    Why SVG

    Logo and icon assets increasingly come from many different workflows.

    One designer may export from Illustrator.

    Another uses Figma.

    A startup team might experiment with an AI logo generator during early visual exploration before a designer cleans up the selected concept.

    From Vite’s perspective, none of those origins matter.

    The build tool sees a file.

    What matters is whether the file entering production satisfies the technical constraints of the application.

    That gives us a useful boundary:

    Creative workflow↓Source SVG↓Technical validation↓Web application

    The plugin doesn’t judge how the artwork was produced.

    It judges whether the resulting asset can safely participate in our frontend workflow.

    That makes the approach useful beyond branding.

    The same architecture could validate:

    UI iconsillustrationsdiagram assetspartner logospayment iconsproduct graphics

    without tying the build process to a particular design tool.

    Validate the Entire Directory

    Now create a function that processes every SVG:

    asyncfunctionvalidateDirectory(directory){const files =awaitfindSvgFiles(directory);const problems =[];for(const file of files){const errors =awaitvalidateSvg(file);if(errors.length>0){problems.push({file,errors});}}return problems;}
    []
    [{file:"src/assets/brand/logo.svg",errors:["missing viewBox attribute","file exceeds 100KB budget"]}]

    The structured result matters.

    Avoid logging errors deep inside the validation function when possible.

    Returning data gives the plugin control over how problems are presented.

    The same validator could later be used by:

    ViteCIa Node CLIunit testsan editor extension

    without rewriting the underlying rules.

    Turn Validation Errors Into Build Errors

    Now connect the validator to buildStart.

    exportfunctionsvgValidator(){const assetDirectory =path.resolve("src/assets/brand");return{name:"svg-validator",asyncbuildStart(){const problems =awaitvalidateDirectory(assetDirectory);if(problems.length===0){return;}const message =formatProblems(problems);this.error(message);}};}
    functionformatProblems(problems){const lines =["SVG validation failed:",""];for(const problem of problems){lines.push(problem.file);for(const error of problem.errors){lines.push(`→${error}`);}lines.push("");}return lines.join("n");}
    logo.svg

    has no viewBox.

    Vite produces a useful failure:

    SVG validation failed:src/assets/brand/logo.svg→ missing viewBox attribute
    this.error(message);

    We aren’t creating an informational report that somebody might read.

    We’re telling the build system:

    The input violates our requirements, so the build should not continue.

    That’s the difference between auditing and enforcement.

    Make Development Feedback Immediate

    Catching invalid assets during vite build is useful.

    Catching them immediately after the developer saves the file is better.

    Vite’s development server exposes its file watcher through the plugin API, so we can attach our validation logic during development.

    configureServer(server){}

    We’ll create a reusable helper first:

    asyncfunctionvalidateOrThrow(directory){const problems =awaitvalidateDirectory(directory);if(problems.length>0){thrownewError(formatProblems(problems));}}

    Then register our directory with the watcher:

    configureServer(server){server.watcher.add(assetDirectory);}
    configureServer(server){server.watcher.add(assetDirectory);server.watcher.on("change",asyncfile=>{if(!file.toLowerCase().endsWith(".svg")){return;}if(!file.startsWith(assetDirectory)){return;}try{const errors =awaitvalidateSvg(file);if(errors.length){console.error(formatProblems([{file,errors}]));}}catch(error){console.error(error);}});}

    Now the feedback loop becomes:

    Developer edits SVG↓Saves file↓Vite watcher detects change↓Plugin validates asset↓Terminal reports problem

    That’s much better than discovering it during a later production build.

    Vite’s current plugin API explicitly exposes the development server, including access to its file-system watcher, through configureServer.

    Don’t Turn the Plugin Into a Second Build System

    Custom developer tooling has a common failure mode.

    validate SVGs

    and six months later it handles:

    SVG validationPNG conversionWebP generationimage resizingfavicon generationmanifest generationCSS generationcomponent generationuploadsdeployment

    At that point, the plugin isn’t a plugin anymore.

    It’s an application hiding inside the build configuration.

    Keep responsibilities narrow.

    Our Vite plugin should answer:

    Are these assets valid according to our project rules?

    It doesn’t necessarily need to:

    Transform every media format our company might ever use.

    That work can live in separate scripts or purpose-built image pipelines.

    This is particularly important for user-uploaded media, which has very different requirements from repository-controlled assets. SitePoint’s recent tutorial on building a fast image upload pipeline in the browser with JavaScript explores that side of image processing, including client-side validation and resizing.

    Our Vite plugin solves a different problem:

    Developer-controlled source assets
    Untrusted files uploaded by users

    Keeping those concerns separate makes both systems easier to reason about.

    Make the Plugin Configurable

    Hard-coded rules are acceptable while experimenting.

    If the plugin proves useful, give it a small configuration API.

    exportfunctionsvgValidator({directory ="src/assets/brand",maxSizeKB =100,requireViewBox =true}={}){}
    svgValidator({directory:"src/assets/icons",maxSizeKB:40});
    const maxBytes =maxSizeKB *1024;
    if(requireViewBox &&!/bviewBoxs*=/i.test(source)){errors.push("missing viewBox attribute");}

    Now the plugin can support different asset groups.

    exportdefaultdefineConfig({plugins:[svgValidator({directory:"src/assets/brand",maxSizeKB:100}),svgValidator({directory:"src/assets/icons",maxSizeKB:30})]});

    The important point isn’t adding endless configuration.

    It’s exposing the requirements that genuinely vary between projects.

    A good developer tool should have strong defaults and a small surface area.

    Give Developers Useful Errors

    Invalid SVG.
    SVG validation failed:src/assets/brand/logo-inverse.svg→ missing viewBox attributesrc/assets/brand/mark.svg→ file exceeds 100KB budget→ <foreignObject> is not allowed

    Both technically communicate failure.

    Only one helps someone fix it quickly.

    Developer experience isn’t just about fast hot-module replacement.

    It’s also about reducing the amount of detective work necessary when something goes wrong.

    As your plugin grows, include enough context to answer:

    Which file failed?Which rule failed?What was expected?How can I fix it?

    For a file-size failure, we could make the message even better:

    const sizeKB =Math.ceil(fileStats.size/1024);errors.push(`file is${sizeKB}KB; maximum is${maxSizeKB}KB`);
    → file is 184KB; maximum is 100KB

    That’s immediately actionable.

    Extract the Validation Engine

    As the implementation matures, separate Vite-specific code from asset-specific code.

    plugins/├── svg-validator/│   ├── index.js│   ├── validate-svg.js│   ├── find-files.js│   └── format-errors.js

    validate-svg.js should know nothing about Vite.

    exportasyncfunctionvalidateSvg(filePath,options){}

    index.js handles the Vite integration:

    exportfunctionsvgValidator(options){return{name:"svg-validator",asyncbuildStart(){},configureServer(server){}};}

    This separation gives us a useful architecture:

    SVG rules↑│Validation engine↑│┌──┴─────────┐│            │Vite plugin   Future CLI

    If you later want developers to run:

    npm run validate:svg

    without starting Vite, the validation logic is already reusable.

    Node.js is particularly well suited to small project utilities like this. SitePoint has a broader tutorial on building command-line tools with Node.js if you decide to expose your validator as a standalone CLI.

    Use the Same Rules in CI

    Local validation improves feedback.

    CI provides enforcement.

    Suppose your production build already runs:

    npm cinpm run build

    Because our validator hooks into Vite’s build lifecycle, an invalid SVG automatically causes:

    npm run build

    to fail.

    No separate CI integration is required.

    Pull request↓Install dependencies↓Run Vite build↓SVG plugin validates assets↓Valid?↙       ↘yes        no↓          ↓build     CI fails

    This is where custom tooling becomes particularly valuable.

    Remember to make sure exported logos have a viewBox.

    depends on somebody remembering the rule forever.

    A plugin doesn’t remember.

    It executes.

    Every time.

    Asset Rules Should Evolve With the Application

    The five checks we’ve implemented are deliberately modest.

    Real applications may have additional requirements.

    A design system might expect every icon to use:

    fill="currentColor"

    rather than hard-coded colors.

    A logo directory might require both:

    logo.svglogo-inverse.svg

    A mobile application might require the compact mark to use a square viewBox.

    A project may prohibit embedded raster data such as:

    <imagehref="data:image/png;base64,..."/>

    You can turn any deterministic requirement into another validation function.

    functioncheckEmbeddedRaster(source){if(/data:image/(?:png|jpeg|webp);base64/i.test(source)){return["embedded raster images are not allowed"];}return[];}
    const validators =[checkViewBox,checkScripts,checkForeignObject,checkEmbeddedRaster];
    for(const validator of validators){errors.push(...validator(source));}

    This is much easier to maintain than a single enormous validation function.

    And it mirrors how developer tooling tends to evolve: small rules composed into a predictable pipeline.

    Be Careful With Build-tool Version Assumptions

    Vite itself continues to evolve.

    SitePoint’s more recent coverage of the Vite 8 production toolchain and migration concerns demonstrates how underlying transformation and build infrastructure can change between major versions.

    That’s another reason to keep custom plugins focused on documented plugin hooks rather than reaching deeply into undocumented Vite internals.

    Our plugin uses a small surface:

    buildStart()configureServer()server.watcherthis.error()

    The less internal knowledge your plugin requires, the easier it is to maintain alongside future build-tool updates.

    Custom tooling should extend your build system.

    It shouldn’t couple your application to implementation details the build system never promised to preserve.

    Where Developer Tooling Pays Off

    The SVG validator we’ve built is intentionally small.

    It won’t win awards for algorithmic complexity.

    That’s part of what makes it useful.

    Some of the best internal developer tools solve problems that are individually tri

    Designer exports SVG↓Developer adds file↓Reviewer hopefully notices issues↓Someone manually optimizes it↓Someone checks dark mode↓Production

    With an explicit build contract:

    Designer exports SVG↓Developer adds file↓Vite validates automatically↓Invalid assets fail immediately↓Production

    We’ve removed several opportunities for inconsistency without creating another manual process.

    That principle extends far beyond SVGs.

    The same Vite plugin architecture could enforce project rules for:

    localization filesJSON configurationMarkdown frontmatterroute metadataenvironment-specific modulesdesign tokenscomponent documentation

    Does our team repeatedly review the same deterministic rule by hand?

    If the answer is yes, there’s probably an opportunity for developer tooling.

    From Convention to Executable Contract

    Teams naturally create conventions.

    All brand SVGs must have viewBox attributes.

    That’s useful documentation.

    But documentation and enforcement solve different problems.

    Here’s what you should do.

    The project won’t accept something else.

    For requirements that can be checked deterministically, the second approach is often much more reliable.

    Our SVG rule began as a sentence:

    Every production logo should have a viewBox.

    It ended as executable JavaScript:

    if(!/bviewBoxs*=/i.test(source)){errors.push("missing viewBox attribute");}

    That’s a small transformation, but it’s a powerful one.

    The knowledge is no longer trapped in a document, code-review checklist, or one experienced developer’s memory.

    It’s part of the build.

    Final Thoughts

    Vite is usually discussed in terms of fast development servers, bundling, framework integrations, and production builds.

    Its plugin system is equally useful for something less glamorous: teaching a project about itself.

    Generic tooling can’t know that your icon files should stay below 30KB.

    It can’t know that your brand assets require viewBox.

    It can’t know which SVG features your team has decided not to allow.

    But your JavaScript can.

    "Remember to check this."
    "The build checks this."

    That’s the real value of project-specific developer tooling.

    Not more configuration.

    Not another dependency.

    Less knowledge that humans have to remember manually.

    Broken Build Catch Plugin Vite
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Tool Tech Team
    • Website

    Related Posts

    A Developer’s Look at Integrating AI Speech Into Applications

    September 11, 2026

    Build a Rust AI Agent Gateway with Tokio and Axum

    September 10, 2026

    Which AI recruiting tool fits your team in 2026?

    September 10, 2026

    WebGPU Shader Syntax Highlighting for Web IDEs

    September 9, 2026

    Dual-Read Cache Consistency in Monolith DB Migrations

    September 9, 2026

    White House takes down ‘Build the Wall’ game after the Tetris Company complains

    September 9, 2026
    Leave A Reply Cancel Reply

    Top posts
    Tech

    Bose QuietComfort Headphones (2nd gen) review: Upgraded in all the right places

    By Tool Tech Team
    Business Software

    Jensen Huang explains why Nvidia will grow an astounding 70% next year

    By Tool Tech Team
    Web Hosting

    A Developer’s Look at Integrating AI Speech Into Applications

    By Tool Tech Team
    Editors Picks

    Bose QuietComfort Headphones (2nd gen) review: Upgraded in all the right places

    September 11, 2026

    Jensen Huang explains why Nvidia will grow an astounding 70% next year

    September 11, 2026

    A Developer’s Look at Integrating AI Speech Into Applications

    September 11, 2026

    Powering AI is an architecture problem

    September 11, 2026
    About Us

    Welcome to ToolTechBlog, your trusted source for the latest insights, reviews, and practical guides on AI tools, business software, cybersecurity, web hosting, and consumer technology.
    Our mission is simple: to help individuals, entrepreneurs, freelancers, students, and businesses discover the right digital tools to improve productivity, streamline workflows, and make informed technology decisions.

    Our Picks

    Bose QuietComfort Headphones (2nd gen) review: Upgraded in all the right places

    September 11, 2026

    Jensen Huang explains why Nvidia will grow an astounding 70% next year

    September 11, 2026

    A Developer’s Look at Integrating AI Speech Into Applications

    September 11, 2026
    Top Reviews

    The AI Hype Index: Unsexy AI

    July 29, 2026

    What it is and How to Fix it

    July 29, 2026

    LG to Ban Residential Proxies from Smart TV Apps

    July 29, 2026

    © 2026 tooltechblog.com. All rights reserved. Designed by DD.

    • About Us
    • Contact Us
    • Terms and Conditions
    • Privacy Policy
    • Disclaimer

    Type above and press Enter to search. Press Esc to cancel.