
Typo3 Vite
- 14 installs
- 33 repo stars
- Updated July 27, 2026
- dirnbauer/webconsulting-skills
Set up a Vite 7 asset pipeline for TYPO3 v13/v14 with per-content-element entrypoints, Bootstrap theming, and PostCSS optimization.
About
This skill configures Vite 7 for TYPO3 v13/v14 projects with SCSS architecture, Bootstrap 5.3 theming, and per-content-element entrypoints. A developer uses it to build a production asset pipeline since v14 removed core asset concatenation and compression.
- Vite 7 asset pipeline for TYPO3 v13/v14 with vite-asset-collector
- Entrypoint-per-content-element splitting and selective Bootstrap 5.3 imports
Typo3 Vite by the numbers
- 14 all-time installs (skills.sh)
- Ranked #958 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dirnbauer/webconsulting-skills --skill typo3-viteAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 33 |
| Last updated | July 27, 2026 |
| Repository | dirnbauer/webconsulting-skills ↗ |
What it does
Set up a Vite 7 asset pipeline for TYPO3 v13/v14 with per-content-element entrypoints, Bootstrap theming, and PostCSS optimization.
Files
TYPO3 Vite Skill
Vite 7 build configuration for TYPO3 v13 and v14 LTS sitepackage development with praetorius/vite-asset-collector. Current gold standard: v14.3 LTS (released 2026-04-21).
v14 context: TYPO3 v14.0 removed the core's built-in frontend CSS/JS concatenation and compression (Breaking #108055) and CSS comment/whitespace stripping (Breaking #107944).config.concatenateCss/compressCss/concatenateJs/compressJsno longer have any effect. An external build tool (Vite / webpack / esbuild) is required for production-grade asset handling on v14.
Key Concepts
Entrypoint-per-CE Pattern
Each content element gets its own Vite entrypoint (*.entry.ts) that imports its SCSS and TypeScript. This enables automatic code splitting -- only the CSS/JS needed for visible content elements is loaded.
Selective Bootstrap Imports
Never import Bootstrap as a whole. Import only the components you use (bootstrap/scss/grid, bootstrap/scss/buttons, etc.) to minimize CSS bundle size.
SVG Optimization
Custom SvgCopyOptimizePlugin processes SVGs from Resources/Private/Svg/ through SVGO and writes optimized files to Resources/Public/Svg/. Supports dev-mode file watching.
CSP Compliance
Assets loaded via <vite:asset> ViewHelper automatically get nonce attributes for Content Security Policy compliance. No inline <script> or <style> tags needed.
Technology Stack
| Layer | Technology |
|---|---|
| Build | Vite 7+ with praetorius/vite-asset-collector |
| CSS | Bootstrap 5.3+ (selective imports, custom theming) |
| PostCSS | autoprefixer + cssnano (production) |
| SCSS | Modern Compiler API (api: 'modern-compiler') |
| SVG | Custom SVGO plugin (SvgCopyOptimizePlugin) |
| Compression | Gzip + Brotli (production) |
| Package Manager | npm, pnpm, or yarn |
References
references/vite-configuration.md-- Complete vite.config.ts, entrypoints, SVG plugin, CSPreferences/scss-architecture.md-- SCSS folder structure, import chain, naming conventions, CSS unitsreferences/bootstrap-theming.md-- Bootstrap variable customization per project
---
Credits & Attribution
This skill is based on the excellent work by [Netresearch DTT GmbH](https://www.netresearch.de/).
Original repository: https://github.com/netresearch/typo3-vite-skill
Copyright (c) Netresearch DTT GmbH — Methodology and best practices (MIT / CC-BY-SA-4.0)
Special thanks to Netresearch DTT GmbH for their generous open-source contributions to the TYPO3 community, which helped shape this skill collection. Adapted by webconsulting.at for this skill collection
Bootstrap Theming Guide
Standard approach for customizing Bootstrap 5.3+ in sitepackage projects.
Theming Flow
Theme/_colors.scss -> Project CI colors
Basic/_variables.scss -> Bootstrap variable overrides
_global-basics.scss -> Loads Bootstrap foundations with overrides
Vendor/_bootstrap.scss -> Selective component imports
Theme/_theme-main.scss -> Post-Bootstrap overridesThe order matters: variables must be defined before Bootstrap processes them.
Essential Variables to Customize
Colors
// Theme/_colors.scss
$color-primary: #0069b4; // Main brand color
$color-secondary: #d1530f; // Accent color
$color-tertiary: #6c757d; // Optional third color
// Additional brand colors (project-specific)
$color-success: #198754;
$color-info: #0dcaf0;
$color-warning: #ffc107;
$color-danger: #dc3545;// Basic/_variables.scss -- Map to Bootstrap
$primary: $color-primary;
$secondary: $color-secondary;
// Extend the Bootstrap color map
$custom-colors: (
'tertiary': $color-tertiary,
);Typography
// Basic/_variables.scss
$font-family-base: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-family-heading: $font-family-base; // Or a different heading font
$font-size-base: 1rem; // 16px
$font-size-sm: 0.875rem; // 14px
$font-size-lg: 1.125rem; // 18px
$font-weight-normal: 400;
$font-weight-bold: 700;
$line-height-base: 1.6;
$line-height-sm: 1.4;
$h1-font-size: 2.5rem;
$h2-font-size: 2rem;
$h3-font-size: 1.5rem;
$h4-font-size: 1.25rem;
$h5-font-size: 1.125rem;
$h6-font-size: 1rem;
$headings-font-weight: 700;
$headings-line-height: 1.2;
$headings-margin-bottom: 0.75rem;Spacing
$spacer: 1rem;
$spacers: (
0: 0,
1: $spacer * 0.25, // 4px
2: $spacer * 0.5, // 8px
3: $spacer, // 16px
4: $spacer * 1.5, // 24px
5: $spacer * 3, // 48px
6: $spacer * 5, // 80px
);Grid and Layout
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px,
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1800px, // Wide content for large screens
);
$grid-columns: 12;
$grid-gutter-width: 1.5rem;Buttons
$btn-padding-y: 0.625rem;
$btn-padding-x: 1.5rem;
$btn-font-size: 1rem;
$btn-font-weight: 600;
$btn-border-radius: 0.25rem;
$btn-padding-y-sm: 0.375rem;
$btn-padding-x-sm: 1rem;
$btn-padding-y-lg: 0.75rem;
$btn-padding-x-lg: 2rem;Forms
$input-padding-y: 0.625rem;
$input-padding-x: 0.75rem;
$input-font-size: 1rem;
$input-border-radius: 0.25rem;
$input-border-color: #ced4da;
$input-focus-border-color: $primary;
$input-focus-box-shadow: 0 0 0 0.2rem rgba($primary, 0.25);Cards
$card-border-radius: 0.5rem;
$card-border-color: rgba(0, 0, 0, 0.1);
$card-spacer-y: 1.25rem;
$card-spacer-x: 1.25rem;
$card-cap-padding-y: 0.75rem;
$card-cap-bg: transparent;Accordion
$accordion-padding-y: 1rem;
$accordion-padding-x: 1.25rem;
$accordion-border-color: rgba(0, 0, 0, 0.125);
$accordion-border-radius: 0.25rem;
$accordion-button-active-bg: $primary;
$accordion-button-active-color: #fff;Navigation
$navbar-padding-y: 0.75rem;
$navbar-padding-x: 1rem;
$nav-link-padding-y: 0.5rem;
$nav-link-padding-x: 1rem;
$nav-link-font-weight: 600;
$navbar-light-color: rgba(0, 0, 0, 0.7);
$navbar-light-hover-color: $primary;
$navbar-light-active-color: $primary;Focus and Accessibility
$focus-ring-width: 0.1875rem;
$focus-ring-opacity: 0.5;
$focus-ring-color: rgba($primary, $focus-ring-opacity);Transitions
$transition-base: all 0.2s ease-in-out;
$transition-fade: opacity 0.15s linear;
$transition-collapse: height 0.35s ease;
$transition-collapse-width: width 0.35s ease;Post-Bootstrap Overrides
For styles that need to override Bootstrap after its CSS is generated:
// Theme/_theme-main.scss
// Custom link styles
a {
text-decoration: none;
transition: color 0.2s ease;
&:hover {
text-decoration: underline;
}
}
// Custom button variants
.btn-primary {
&:hover {
background-color: darken($primary, 10%);
}
}
// Custom container padding
.container,
.container-fluid {
padding-left: 1.5rem;
padding-right: 1.5rem;
@media (min-width: map-get($grid-breakpoints, lg)) {
padding-left: 4.5rem;
padding-right: 4.5rem;
}
}Theme Checklist
When setting up a new project theme:
- [ ] CI colors defined in
Theme/_colors.scss - [ ] Colors mapped to Bootstrap variables in
Basic/_variables.scss - [ ] All color combinations meet WCAG AA contrast (4.5:1 text, 3:1 UI)
- [ ] Typography variables set (font family, sizes, weights)
- [ ] Local font files in
Resources/Public/Fonts/ - [ ]
@font-facedeclarations withfont-display: swap - [ ] Spacing scale defined
- [ ] Container max-widths adjusted
- [ ] Button and form styles customized
- [ ] Focus ring visible and uses primary color
- [ ] Only needed Bootstrap components imported in
Vendor/_bootstrap.scss
SCSS Architecture
Folder Structure
Resources/Private/Scss/
├── main.scss # Main entrypoint (imports everything)
├── rte.scss # Rich Text Editor styles
├── _global-basics.scss # Bootstrap foundations + theme variables
├── Basic/ # Foundation styles
│ ├── _variables.scss # Colors, spacers, fonts, breakpoints (Bootstrap overrides)
│ ├── _fonts.scss # @font-face declarations (local files only)
│ ├── _typography.scss # Headings, body text, links
│ ├── _accessibility.scss # Focus styles, skip links, screen reader utilities
│ ├── _images.scss # img-fluid, figure, picture defaults
│ ├── _tables.scss # Table styling
│ └── _spacing.scss # Custom spacing utilities beyond Bootstrap
├── Components/ # Reusable UI components
│ ├── _button.scss
│ ├── _card.scss
│ ├── _forms.scss
│ ├── _pagination.scss
│ ├── _alert.scss
│ └── _<component>.scss
├── ContentElements/ # One partial per TYPO3 content element
│ ├── _accordion.scss
│ ├── _textmedia.scss
│ ├── _slider.scss
│ ├── _video.scss
│ └── _<element>.scss
├── Page/ # Page-level layout components
│ ├── _header.scss
│ ├── _navigation.scss
│ ├── _hero.scss
│ ├── _footer.scss
│ ├── _breadcrumb.scss
│ └── _logo.scss
├── Plugins/ # Third-party extension overrides
│ ├── Solr/
│ ├── sf_event_mgt/
│ └── News/
├── Vendor/ # Third-party CSS imports
│ ├── _bootstrap.scss # Selective Bootstrap imports
│ ├── _flatpickr.scss
│ └── _simple-lightbox.scss
├── Theme/ # Project-specific theming
│ ├── _colors.scss # CI colors mapped to Bootstrap variables
│ └── _theme-main.scss # Theme-specific overrides
└── Mixins/ # Custom SCSS mixins
└── _spritemap-mixin.scss # Auto-generated by Vite SVG plugin (if used)Import Chain
The main.scss file is the single entrypoint that orchestrates all imports:
// main.scss
@import 'global-basics'; // Theme + Bootstrap foundations
@import 'Basic/variables';
@import 'Basic/fonts';
@import 'Basic/typography';
@import 'Basic/accessibility';
@import 'Basic/images';
@import 'Basic/tables';
@import 'Basic/spacing';
@import 'Vendor/bootstrap'; // Selective Bootstrap component imports
@import 'Components/button';
@import 'Components/card';
// ... remaining components
@import 'ContentElements/accordion';
// ... remaining content elements
@import 'Page/header';
@import 'Page/navigation';
@import 'Page/footer';
@import 'Plugins/Solr/search';_global-basics.scss
This file loads the Bootstrap foundation in the correct order:
// 1. Theme colors and custom variables
@import 'Theme/colors';
// 2. Bootstrap foundations (order matters)
@import 'bootstrap/scss/functions';
@import 'Basic/variables'; // Must come after functions, before Bootstrap variables
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/variables-dark';
@import 'bootstrap/scss/maps';
@import 'bootstrap/scss/mixins';
@import 'bootstrap/scss/utilities';
// 3. Theme overrides
@import 'Theme/theme-main';Bootstrap Integration
Import Bootstrap selectively -- never @import 'bootstrap' as a whole:
// Vendor/_bootstrap.scss
@import 'bootstrap/scss/root';
@import 'bootstrap/scss/reboot';
@import 'bootstrap/scss/type';
@import 'bootstrap/scss/images';
@import 'bootstrap/scss/containers';
@import 'bootstrap/scss/grid';
@import 'bootstrap/scss/buttons';
@import 'bootstrap/scss/nav';
@import 'bootstrap/scss/navbar';
@import 'bootstrap/scss/card';
@import 'bootstrap/scss/accordion';
@import 'bootstrap/scss/dropdown';
@import 'bootstrap/scss/utilities/api';
// Only import components you actually useNaming Conventions (Bootstrap-Style)
Use Bootstrap-style naming -- hyphenated lowercase with component prefixes. Double underscores (__) are acceptable for child elements within components. Use state classes instead of BEM modifier syntax (--modifier).
Pattern
{component}-{element} → nav-link, card-body, accordion-header
{component}__{child} → ce-teaser__body, main-nav__link (child elements)
{component}-{variant} → btn-primary, btn-outline-secondary, alert-danger
{state} → active, show, collapsed, disabled, is-open
{responsive}-{utility} → d-lg-flex, text-md-centerCSS Classes
| Context | Pattern | Examples |
|---|---|---|
| Bootstrap utilities | Use as-is | .d-flex, .bg-primary, .w-100, .mt-3 |
| Content elements | .ce-{name} | .ce-accordion, .ce-slider, .ce-teaser |
| CE children | .ce-{name}__{part} or .ce-{name}-{part} | .ce-teaser__body, .ce-accordion-header |
| Page sections | .main-{section} | .main-header, .main-content, .main-footer |
| Navigation | .main-nav__{part} | .main-nav__list, .main-nav__link |
| Custom components | {component}__{part} | .skiplinks__link, .error-page__title |
| State | Same as Bootstrap | .active, .show, .collapsed, .is-visible, .has-children |
| TYPO3 frames | .frame-{type} | .frame, .frame-type-textmedia, .frame-layout-dark |
Anti-Patterns (do NOT use)
// BAD: BEM modifier syntax
.accordion__header--active { }
.step-indicator__item--completed { }
// GOOD: State classes
.accordion__header.active { }
.step-indicator__item.completed { }
// BAD: div soup without semantic classes
.wrapper > .inner > .content { }
// GOOD: Component-scoped naming
.ce-teaser__body { }Files
- All SCSS partials use underscore prefix:
_component-name.scss - One file per component/content element
- File names match the component they style
CSS Units
- rem/em for everything -- font sizes, spacing, margins, padding (follows Bootstrap)
- px only for:
1pxborders and box-shadows - Use rem equivalents:
0.0625rem(1px),0.125rem(2px),0.1875rem(3px) - Never mix units within the same spacing system
- Base font size: 1rem (16px browser default)
Variables
Override Bootstrap defaults in Basic/_variables.scss (colors, spacing, fonts, breakpoints, container widths). See references/bootstrap-theming.md for the complete list of variables to customize per project.
Fonts
Always load fonts locally:
// Basic/_fonts.scss
@font-face {
font-family: 'Open Sans';
src:
url('../Fonts/OpenSans-Regular.woff2') format('woff2'),
url('../Fonts/OpenSans-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}Place font files in Resources/Public/Fonts/. Use font-display: swap for performance.
Content Element Pattern
Each content element gets its own SCSS partial:
// ContentElements/_accordion.scss
.ce-accordion {
.accordion-item {
border-radius: 0;
}
.accordion-button {
font-weight: $font-weight-bold;
&:not(.collapsed) {
background-color: $primary;
color: $white;
}
}
}The filename corresponds to the Vite entrypoint: if there's accordion.entry.ts, there should be _accordion.scss.
Vite Configuration
Overview
Vite is the standard build tool, integrated with TYPO3 via praetorius/vite-asset-collector. The configuration handles:
- TypeScript compilation
- SCSS processing with PostCSS (autoprefixer + cssnano)
- Per-content-element code splitting via entrypoints
- SVG optimization via custom plugin
- Image optimization
- Gzip + Brotli compression (production)
- HMR for development
vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import { compression } from 'vite-plugin-compression2';
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
import autoOrigin from 'vite-plugin-auto-origin';
import { SvgCopyOptimizePlugin } from './vite.helpers';
const isProduction = process.env.NODE_ENV === 'production';
export default defineConfig({
publicDir: false,
build: {
manifest: true,
rollupOptions: {
input: {
'main': resolve(__dirname, 'Resources/Private/Entrypoints/main.entry.ts'),
'accordion': resolve(__dirname, 'Resources/Private/Entrypoints/accordion.entry.ts'),
// ... one entry per content element / page feature
'rte': resolve(__dirname, 'Resources/Private/Scss/rte.scss'),
},
output: {
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.css')) return 'css/[name]-[hash][extname]';
if (assetInfo.name?.match(/\.(woff2?|ttf|eot)$/)) return 'fonts/[name]-[hash][extname]';
if (assetInfo.name?.match(/\.(png|jpe?g|gif|svg|webp|avif)$/)) return 'images/[name]-[hash][extname]';
return 'assets/[name]-[hash][extname]';
},
manualChunks: (id) => {
if (id.includes('node_modules')) {
return id.split('node_modules/').pop()?.split('/')[0];
}
},
},
},
outDir: resolve(__dirname, 'Resources/Public/Build'),
emptyOutDir: true,
cssCodeSplit: true,
assetsInlineLimit: 100, // Prevents SVG inlining (needed for SvgIconProvider)
minify: isProduction ? 'terser' : false,
},
css: {
postcss: {
plugins: [
autoprefixer(),
...(isProduction ? [cssnano()] : []),
],
},
preprocessorOptions: {
scss: {
api: 'modern-compiler',
additionalData: `$mode: "${isProduction ? 'production' : 'development'}";`,
},
},
},
plugins: [
autoOrigin(),
SvgCopyOptimizePlugin(),
...(isProduction ? [
ViteImageOptimizer({
png: { quality: 80 },
jpeg: { quality: 80 },
webp: { quality: 80 },
}),
compression({ algorithm: 'gzip' }),
compression({ algorithm: 'brotliCompress' }),
] : []),
],
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
origin: 'http://localhost:5173',
// Required for Vite 7.3+ / 8.x when accessed via reverse proxy (Traefik etc.)
// Without these, the dev server returns HTTP 403 "Blocked request" for any
// host header other than 'localhost' — even if the host appears to be
// routed correctly. true = allow all hosts (use array for narrower scope).
allowedHosts: true,
cors: true,
},
});Anti-pattern: duplicate `server:` blocks. Define server: exactly once indefineConfig({ ... }). JavaScript object literals silently overwriteearlier keys with later ones, so two server: { ... } blocks lose the firstone's options without warning. If you add allowedHosts, put it inside theexisting server block — do not create a second one.Vite 7.1.x quirk. Versions before 7.3 do not enforce host-header checks
by default, so a missing allowedHosts works "by accident". Upgrading to7.3+ or 8.x will break HMR behind a proxy unless allowedHosts is set.Entrypoints
Each content element or page feature gets its own entrypoint in Resources/Private/Entrypoints/:
main.entry.ts # Main entrypoint (always loaded)
accordion.entry.ts # Only loaded on pages with accordions
slider.entry.ts # Only loaded on pages with sliders
lightbox.entry.ts # Only loaded on pages with lightboxes
solr.entry.ts # Only loaded on search pagesEntrypoint Pattern
// main.entry.ts
import '../Scss/main.scss';
import { Collapse, Dropdown } from 'bootstrap';
import { initStickyHeader } from '../TypeScript/Plugins/stickyheader';
import { initNavHelper } from '../TypeScript/Plugins/navhelper';
document.addEventListener('DOMContentLoaded', () => {
initStickyHeader();
initNavHelper();
});// accordion.entry.ts
import '../Scss/ContentElements/_accordion.scss';
import { initAccordionStacked } from '../TypeScript/Plugins/accordion-stacked';
document.addEventListener('DOMContentLoaded', () => {
const accordions = document.querySelectorAll('.ce-accordion');
if (accordions.length > 0) {
initAccordionStacked();
}
});Fluid Integration
Include entrypoints in content element templates:
<vite:asset entry="EXT:my_sitepackage/Resources/Private/Entrypoints/accordion.entry.ts" />The main.entry.ts is included in the page layout (loaded on every page).
SVG Optimization Plugin
The custom SvgCopyOptimizePlugin processes SVGs from Resources/Private/Svg/ to Resources/Public/Svg/:
- Reads SVGs from
Resources/Private/Svg/ - Optimizes with SVGO (using
svgo.config.jsif present) - Slugifies filenames (lowercase, hyphens)
- Writes optimized files to
Resources/Public/Svg/ - Watches for changes in dev mode (add/change/delete)
- Tracks processed files to avoid redundant work
- Skips re-optimization when the output file is newer than its source (see below)
- Logs optimization stats (original size vs optimized)
The plugin source lives in vite.helpers.ts and is imported in vite.config.ts.
Skipping unchanged sources between builds
The in-memory processedFiles map only protects against duplicate work within a single Vite process. Between builds (vite build re-runs, fresh container starts, CI), the map is empty, so without an additional check every SVG would be re-optimized on every build.
Compute the output path before reading the source, then short-circuit when output.mtime >= source.mtime:
const parsed = resolve(rel).split('/').pop().replace('.svg', '');
const outName = slugify(parsed) + '.svg';
const outPath = resolve(outDir, outName);
// Skip re-optimization if the existing output is newer than the source.
// Saves the read + SVGO call entirely on subsequent builds.
if (!changedFile && existsSync(outPath)) {
const outStats = statSync(outPath);
if (outStats.mtime.getTime() >= lastModified) {
processedFiles.set(srcPath, Date.now());
skippedFiles.push(`Public/Svg/${outName}`);
continue;
}
}
// only reached when the output is missing or older than the source:
const raw = await fs.readFile(srcPath, 'utf8');
const result = optimize(raw, { path: srcPath, ...svgoConfig });
await fs.writeFile(outPath, result.data, 'utf8');Two important orderings:
1. outPath must be computed before the existsSync check (and therefore before fs.readFile/optimize()). Otherwise the skip block has nothing to compare against and the savings disappear. 2. The !changedFile guard ensures explicit dev-server change/add events always re-optimize. The skip only triggers in full-build runs.
Caveat: this relies on filesystem mtimes. On CI runners that wipe Resources/Public/Svg/ between jobs, the skip cannot fire — either commit the optimized output, cache the directory between pipeline runs, or layer a content-hash manifest on top.
svgo.config.js
export default {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
cleanupIds: false,
},
},
},
'removeDimensions',
],
};HMR (Hot Module Replacement)
For local development:
1. Run the Vite dev server (e.g. npm run hmr) on port 5173 2. The vite-asset-collector extension detects the dev server and serves assets from it 3. CSS changes are injected without page reload 4. TypeScript changes trigger a page reload
Manifest Mode (No Dev Server)
Not every workflow runs an HMR server. If assets are built at install or image-build time (vite build) and only the manifest is ever served, force manifest mode — the extension default useDevServer = auto resolves to Environment::getContext()->isDevelopment(), so any Development/* context makes <vite:asset> chase a dev server that is not running:
<?php
// config/system/settings.php
return [
'EXTENSIONS' => [
'vite_asset_collector' => [
'useDevServer' => '0',
'devServerUri' => 'auto',
'defaultManifest' => '_assets/vite/.vite/manifest.json',
],
],
];Two non-obvious rules apply:
- Pre-populate the complete key set (
useDevServer,devServerUri,
defaultManifest) — not just the key you override. On sites that keep settings.php read-only (e.g. sealed chmod 0444, secret-free managed config), ExtensionConfiguration::get() with a path missing from settings.php triggers a synchronize that writes the file; on the sealed file that throws core exception #1346323822 ("settings.php is not writable") and the frontend returns HTTP 500. A complete block keeps every read path valid, so no write is ever attempted.
- The defaults align by design:
vite-plugin-typo3project mode outputs to
<web-dir>/_assets/vite/ with the manifest at _assets/vite/.vite/manifest.json — exactly the extension's defaultManifest. <vite:asset entry="EXT:..." /> therefore needs no manifest argument.
For Docker/deployment images that COPY extension or package directories, keep package.json and vite.config.js at the Composer project root (not inside an extension): node_modules then never sits inside a copied path, and only the built _assets/vite/ output ships.
CSP Compliance
The vite-asset-collector supports nonce-based asset inclusion for Content Security Policy:
- Assets loaded via
<vite:asset>automatically get the correct nonce - No inline
<script>or<style>tags needed - Configure CSP headers in TYPO3's Content-Security-Policy API or web server config
- The
autoOriginplugin ensures HMR works with CSP in development
package.json Scripts
Configure build and lint scripts in your project's package.json as needed.