[{"data":1,"prerenderedAt":908},["ShallowReactive",2],{"blog-headless-product-configurator-b2b-en":3},{"slug":4,"published":5,"minutes":6,"category":7,"tags":8,"keywords":13,"about":24,"sources":31,"cover":58,"og":59,"expertise":60,"locales":61,"lang":62,"title":65,"description":66,"coverAlt":67,"metaTitle":68,"takeaways":69,"faq":75,"toc":94,"blocks":131,"others":620},"headless-product-configurator-b2b","2026-10-02",12,"web",[9,10,11,12],"Product configurator","B2B e-commerce","Nuxt","Headless commerce",[14,15,16,17,18,19,20,21,22,23],"B2B product configurator","Produktkonfigurator","Produktkonfigurator TYPO3","B2B Konfigurator","headless product configurator","CPQ configure price quote","rule engine vs constraint solver","Nuxt Spryker Glue API","product configurator 100000 variants","accessible product configurator",[25,27,29],{"name":9,"url":26},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct_configurator",{"name":12,"url":28},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_commerce",{"name":11,"url":30},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FNuxt",[32,34,37,40,43,46,49,52,55],{"title":33,"url":26},"Wikipedia: Knowledge-based configuration (product configurator)",{"title":35,"url":36},"Google OR-Tools: The CP-SAT solver","https:\u002F\u002Fdevelopers.google.com\u002Foptimization\u002Fcp\u002Fcp_solver",{"title":38,"url":39},"Spryker documentation: Glue API, Product Configuration feature integration","https:\u002F\u002Fdocs.spryker.com\u002Fdocs\u002Fscos\u002Fdev\u002Ffeature-integration-guides\u002F202108.0\u002Fglue-api\u002Fglue-api-product-configuration-feature-integration.html",{"title":41,"url":42},"OWASP: Input Validation Cheat Sheet","https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FInput_Validation_Cheat_Sheet.html",{"title":44,"url":45},"web.dev: Virtualize large lists","https:\u002F\u002Fweb.dev\u002Farticles\u002Fvirtualize-long-lists-react-window",{"title":47,"url":48},"W3C: Understanding SC 3.3.1 Error Identification (WCAG 2.2)","https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG22\u002FUnderstanding\u002Ferror-identification.html",{"title":50,"url":51},"W3C: Understanding SC 4.1.3 Status Messages (WCAG 2.2)","https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG22\u002FUnderstanding\u002Fstatus-messages.html",{"title":53,"url":54},"W3C: Understanding SC 2.5.8 Target Size (Minimum) (WCAG 2.2)","https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG22\u002FUnderstanding\u002Ftarget-size-minimum.html",{"title":56,"url":57},"W3C WAI-ARIA Authoring Practices: Combobox pattern","https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002Fpatterns\u002Fcombobox\u002F","\u002Fimages\u002Fblog\u002Fheadless-product-configurator-b2b\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fheadless-product-configurator-b2b\u002Fog.jpg","b2b-ecommerce-developer",[62,63,64],"en","de","hu","Headless B2B product configurator: rules, pricing and Nuxt on a commerce API","How to build a B2B product configurator headless: rule engine or solver, where rules live, server-side pricing, Nuxt on a commerce API, TYPO3 and 100k+ variants.","Diagram: a Nuxt front end talks to a configuration service that reads rules from the PIM and prices from the ERP, then hands a validated configuration to the commerce API.","Headless B2B product configurator guide · Balázs Csorba",[70,71,72,73,74],"Treat the configurator as a small service with three jobs (what is allowed, what it costs, what the shop needs to sell it), not as a front-end widget with rules baked in.","Start with a declarative rule engine; reach for a constraint solver only when rules interact so much that you cannot enumerate valid combinations or explain a conflict.","Rules belong where the product data is maintained (usually the PIM), prices where they are governed (usually the ERP), and the shop only receives a validated result.","The browser may preview, but the server must validate and price again at add-to-cart and at quote time, because client-side validation is trivially bypassed.","With 100,000+ variants you never ship the variant list: you ask the server for the next valid options, and you make that dialogue keyboard- and screen-reader-friendly.",[76,79,82,85,88,91],{"q":77,"a":78},"What is a B2B product configurator?","A B2B product configurator guides a buyer through choosing a valid combination of options for a configurable product, such as dimensions, materials and tolerances, shows the price and lead time, and passes the result to a cart or quote. Compared with consumer configurators, the rules are stricter, the catalogue is larger, and the price often depends on contract terms.",{"q":80,"a":81},"Rule engine or constraint solver for a product configurator?","Start with a declarative rule engine: it is easy to explain, test and let product managers maintain. Move to a constraint solver when rules interact so heavily that valid combinations cannot be enumerated or hand-ordered, or when you need to explain why a selection is impossible and find the nearest valid alternative.",{"q":83,"a":84},"Where should configurator rules live: PIM, ERP or shop?","Keep the rules next to the product data that justifies them, which is usually the PIM, and keep prices where they are governed, usually the ERP. The shop should consume a validated configuration, not own the rules, otherwise rules end up duplicated in the shop and the ERP and drift apart.",{"q":86,"a":87},"How do you build a Produktkonfigurator with TYPO3?","Let TYPO3 own content, landing pages and navigation, and mount the configurator as a headless app or a plugin that talks to a separate configuration service. TYPO3 passes only context such as language, customer group and a product identifier; rules, prices and validation stay out of the CMS.",{"q":89,"a":90},"How do you handle 100,000+ variants in a configurator?","Do not load or render all variants. Model the product as attributes and rules, let the server return only the options that are still valid for the current selection, index searchable attributes in a search engine, cache the option lookups, and virtualise any long list you do have to show.",{"q":92,"a":93},"How do you make a configurator accessible?","Use native form controls or well-tested ARIA patterns, announce price and validation changes as status messages without moving focus, describe errors in text next to the field, and keep targets at least 24 by 24 CSS pixels. Test the whole flow with a keyboard and a screen reader, not only with an automated checker.",[95,98,101,104,107,110,113,116,119,122,125,128],{"id":96,"title":97},"what-it-does","What a B2B configurator actually has to do",{"id":99,"title":100},"rule-engine-or-solver","Rule engine or constraint solver",{"id":102,"title":103},"where-rules-live","Where the rules live: PIM, ERP or shop",{"id":105,"title":106},"pricing","Pricing logic without leaking it",{"id":108,"title":109},"nuxt-front-end","The Nuxt front end on a commerce API",{"id":111,"title":112},"typo3-cms","Integrating TYPO3 or another CMS",{"id":114,"title":115},"performance","Performance with 100,000+ variants",{"id":117,"title":118},"validation-quotes","Server-side validation, saving and quoting",{"id":120,"title":121},"accessibility","Accessibility is a configurator requirement",{"id":123,"title":124},"checklist","A launch checklist",{"id":126,"title":127},"outlook","Where this leaves you",{"id":129,"title":130},"sources","Sources",[132,136,149,152,171,178,179,187,202,205,271,274,275,282,285,294,297,320,321,328,331,350,353,354,361,383,401,402,409,412,413,419,455,456,463,470,492,499,500,503,545,548,549,552,571,574,575,578,590,591],{"type":133,"content":134},"paragraph",[135],"A product configurator looks like a front-end problem until the first real catalogue arrives. Then it turns out to be a data problem (where do the rules come from?), a pricing problem (who is allowed to say what this costs?) and a trust problem (can the shop sell exactly what the buyer configured?). In B2B, where a wrong combination means a wrong part on a machine, the last one matters most.",{"type":133,"content":137},[138,139,142,143,148],"This is how I would build a ",{"tag":140,"children":141},"strong",[14]," (a \"CPQ-lite\": configure, price, quote, without the full CPQ suite) headless in 2026. It draws on a guided configurator for more than 100,000 precision engineering articles with automated pricing, built in Nuxt.js on the Spryker Glue API (see ",{"tag":144,"to":145,"children":146},"link","\u002Freferences",[147],"the Meusburger reference","). The article stays at the level of architecture and trade-offs and does not describe that project beyond that.",{"type":150,"level":151,"id":96,"text":97},"heading",2,{"type":133,"content":153},[154,155,158,159,162,163,166,167,170],"Strip away the UI and a configurator does four things. It ",{"tag":140,"children":156},[157],"constrains"," choices so that only valid combinations are reachable. It ",{"tag":140,"children":160},[161],"derives"," values the buyer should not type (article number, weight, lead time). It ",{"tag":140,"children":164},[165],"prices"," the result, often with tiered or contract prices. And it ",{"tag":140,"children":168},[169],"hands over"," a configuration the rest of the business can use: a cart line, a quote, a drawing request.",{"type":133,"content":172},[173,174,177],"The recurring mistake is putting all four inside the front end, because that is where the demo is built. The result is rules that cannot be tested without a browser, prices the client can see and, worse, influence, and a shop that has to trust whatever arrives. I would split the system into a thin UI and a ",{"tag":140,"children":175},[176],"configuration service"," that owns the first three jobs, and let the commerce platform own the fourth.",{"type":150,"level":151,"id":99,"text":100},{"type":133,"content":180},[181,182,186],"Historically, configurators started with production rules; the model-based, constraint-based approaches that followed separate product knowledge from the problem-solving strategy, so that changes to one do not break the other, as the ",{"tag":183,"href":26,"children":184},"a",[185],"Wikipedia article on knowledge-based configuration"," describes. That separation is the practical point: whichever technique you pick, the rules should be data, not code paths.",{"type":133,"content":188},[189,190,193,194,197,198,201],"A ",{"tag":140,"children":191},[192],"declarative rule engine"," evaluates conditions such as \"if material is X, then diameters above Y are not allowed\" against the current selection. It is easy to explain to product managers, easy to unit-test (selection in, allowed options out) and fast. A ",{"tag":140,"children":195},[196],"constraint solver"," such as ",{"tag":183,"href":36,"children":199},[200],"OR-Tools CP-SAT"," treats the product as variables and constraints and searches for valid assignments; note that it works over integers, so decimal dimensions need scaling. It shines when constraints interact in ways you cannot order by hand, and when you want to answer \"what is the closest valid configuration?\".",{"type":133,"content":203},[204],"My default is the rule engine, with the rules in a format that a solver could consume later. Most catalogue products are really families with a few dozen interacting parameters, and the explainability of a rule (\"this option is disabled because of rule 42\") is worth more to a support team than the generality of a solver. The table is the decision aid I would use.",{"type":206,"head":207,"rows":216},"table",[208,210,212,214],[209],"Situation",[211],"Use",[213],"Why",[215],"Watch out for",[217,228,239,249,260],[218,220,224,226],[219],"Product families with parameter ranges and a few dozen dependencies",[221],{"tag":140,"children":222},[223],"Rule engine",[225],"Transparent, testable, maintainable by product management",[227],"Rule order and overlapping rules; keep them declarative and unit-tested",[229,231,235,237],[230],"Many interacting options, no clear evaluation order",[232],{"tag":140,"children":233},[234],"Constraint solver",[236],"Finds valid assignments and conflicts without hand-written order",[238],"Integer modelling, response time, explaining failures to users",[240,242,245,247],[241],"\"Nothing is valid, what is closest?\" must be answered",[243],{"tag":140,"children":244},[234],[246],"Can relax constraints and search for alternatives",[248],"Needs a clear objective for what \"closest\" means",[250,252,256,258],[251],"Mostly fixed variants, selection is a filter",[253],{"tag":140,"children":254},[255],"Faceted search, no engine",[257],"The variants already exist as articles; filter and sort them",[259],"Do not build a configurator where a search page works",[261,263,267,269],[262],"Rules owned by non-developers who change them weekly",[264],{"tag":140,"children":265},[266],"Rule engine with an editor",[268],"Rules as data with versioning and review",[270],"Governance: who may publish a rule, and how is it tested",{"type":133,"content":272},[273],"Before building anything, check that you need a configurator at all. If the variants exist as sellable articles, a good filterable catalogue is cheaper and more robust. A configurator earns its cost where the combination space is too large to enumerate or where values are derived, not chosen.",{"type":150,"level":151,"id":102,"text":103},{"type":133,"content":276},[277,278,281],"The question that decides maintenance cost in year two is not which engine, but ",{"tag":140,"children":279},[280],"who owns each kind of knowledge",". My rule of thumb: rules sit next to the product data that justifies them (typically the PIM), prices sit where they are governed (typically the ERP, with the shop caching them), and the shop never owns configuration logic. It owns the cart, the checkout and the customer relationship.",{"type":133,"content":283},[284],"The architecture below puts a configuration service between the Nuxt front end and the commerce API. The service reads attributes and rules from the PIM, prices and availability from the ERP, and returns options, derived values, price and a validation result. The commerce API only ever receives a configuration that this service has validated.",{"type":286,"attrs":287,"inner":291,"caption":292},"diagram",{"viewBox":288,"role":289,"aria-labelledby":290},"0 0 720 350","img","d1-cfg-t d1-cfg-d","\u003Ctitle id=\"d1-cfg-t\">Headless configurator: who talks to whom\u003C\u002Ftitle>\u003Cdesc id=\"d1-cfg-d\">A Nuxt front end sends the current selection to a configuration service, which reads rules from the PIM and prices from the ERP. The service returns valid options, price and derived values. A validated configuration goes to the commerce API for cart and quote. The CMS supplies content to the front end.\u003C\u002Fdesc>\u003Ctext x=\"20\" y=\"28\" class=\"d-title\">Headless configurator: who talks to whom\u003C\u002Ftext>\u003Ctext x=\"700\" y=\"28\" text-anchor=\"end\" class=\"d-label\">Architecture sketch\u003C\u002Ftext>\u003Crect x=\"20\" y=\"70\" width=\"160\" height=\"76\" rx=\"10\" class=\"d-sky\" \u002F>\u003Ctext x=\"100\" y=\"104\" text-anchor=\"middle\" class=\"d-text\">Nuxt front end\u003C\u002Ftext>\u003Ctext x=\"100\" y=\"125\" text-anchor=\"middle\" class=\"d-small\">guided steps, preview\u003C\u002Ftext>\u003Cpath d=\"M180 108 H277\" class=\"d-line\" \u002F>\u003Cpath d=\"M285 108 l-9 -5 v10 z\" class=\"d-head\" \u002F>\u003Crect x=\"285\" y=\"70\" width=\"160\" height=\"76\" rx=\"10\" class=\"d-accent\" \u002F>\u003Ctext x=\"365\" y=\"104\" text-anchor=\"middle\" class=\"d-text\">Configuration service\u003C\u002Ftext>\u003Ctext x=\"365\" y=\"125\" text-anchor=\"middle\" class=\"d-small\">rules, price, validate\u003C\u002Ftext>\u003Cpath d=\"M445 108 H532\" class=\"d-line\" \u002F>\u003Cpath d=\"M540 108 l-9 -5 v10 z\" class=\"d-head\" \u002F>\u003Crect x=\"540\" y=\"70\" width=\"160\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"620\" y=\"104\" text-anchor=\"middle\" class=\"d-text\">Commerce API\u003C\u002Ftext>\u003Ctext x=\"620\" y=\"125\" text-anchor=\"middle\" class=\"d-small\">cart, quote, checkout\u003C\u002Ftext>\u003Cpath d=\"M300 200 V152\" class=\"d-line\" \u002F>\u003Cpath d=\"M300 146 l-5 9 h10 z\" class=\"d-head\" \u002F>\u003Cpath d=\"M435 200 V152\" class=\"d-line\" \u002F>\u003Cpath d=\"M435 146 l-5 9 h10 z\" class=\"d-head\" \u002F>\u003Crect x=\"240\" y=\"200\" width=\"120\" height=\"56\" rx=\"10\" class=\"d-mint\" \u002F>\u003Ctext x=\"300\" y=\"224\" text-anchor=\"middle\" class=\"d-text\">PIM\u003C\u002Ftext>\u003Ctext x=\"300\" y=\"245\" text-anchor=\"middle\" class=\"d-small\">attributes, rules\u003C\u002Ftext>\u003Crect x=\"375\" y=\"200\" width=\"120\" height=\"56\" rx=\"10\" class=\"d-mint\" \u002F>\u003Ctext x=\"435\" y=\"224\" text-anchor=\"middle\" class=\"d-text\">ERP\u003C\u002Ftext>\u003Ctext x=\"435\" y=\"245\" text-anchor=\"middle\" class=\"d-small\">prices, stock\u003C\u002Ftext>\u003Cpath d=\"M100 200 V152\" class=\"d-line\" \u002F>\u003Cpath d=\"M100 146 l-5 9 h10 z\" class=\"d-head\" \u002F>\u003Crect x=\"20\" y=\"200\" width=\"160\" height=\"56\" rx=\"10\" class=\"d-gold\" \u002F>\u003Ctext x=\"100\" y=\"224\" text-anchor=\"middle\" class=\"d-text\">CMS\u003C\u002Ftext>\u003Ctext x=\"100\" y=\"245\" text-anchor=\"middle\" class=\"d-small\">content, context\u003C\u002Ftext>\u003Crect x=\"20\" y=\"290\" width=\"680\" height=\"40\" rx=\"10\" class=\"d-box d-dash\" \u002F>\u003Ctext x=\"360\" y=\"315\" text-anchor=\"middle\" class=\"d-small\">The server validates again at add-to-cart and quote. The browser only previews.\u003C\u002Ftext>",[293],"The configuration service is the only place that combines rules, prices and validation. The browser previews; the commerce API receives validated configurations only.",{"type":133,"content":295},[296],"Three consequences follow from this split.",{"type":298,"ordered":299,"items":300},"list",false,[301,306,311],[302,305],{"tag":140,"children":303},[304],"One source per fact."," If a rule exists in the PIM and again in the shop, one of them will be wrong within a quarter. Publish rules from the PIM into the service (for example as a versioned artefact) instead of re-entering them.",[307,310],{"tag":140,"children":308},[309],"The service is stateless over a selection."," Every call carries the full selection and the context (customer, price list, language). That makes it cacheable, testable and horizontally scalable.",[312,315,316,319],{"tag":140,"children":313},[314],"The shop sees a result, not a process."," Spryker, for instance, documents a Product Configuration feature with Glue API modules that carry configuration data on cart items (",{"tag":183,"href":39,"children":317},[318],"Spryker documentation","). Whatever platform you use, check how it stores a configuration on a cart line and whether it can price it, before you design your own format.",{"type":150,"level":151,"id":105,"text":106},{"type":133,"content":322},[323,324,327],"B2B prices are rarely a single number. They combine a base price, option surcharges, quantity tiers, customer-specific agreements and sometimes machining or setup costs. I would keep the ",{"tag":140,"children":325},[326],"price calculation on the server, in the configuration service",", as a pure function of selection, quantity and customer context, and have it call the ERP or the price list the ERP maintains.",{"type":133,"content":329},[330],"The front end then shows a price that the server returned and labels it as such. If the buyer changes quantity, ask again. Never compute the final price from numbers that were sent to the browser; those numbers are inputs for display, not for ordering.",{"type":332,"variant":333,"title":334,"body":335},"callout","tip","Make price reproducible",[336],[337,338,341,342,345,346,349],"Store, with every saved configuration and quote, the ",{"tag":140,"children":339},[340],"rule-set version",", the ",{"tag":140,"children":343},[344],"price-list version"," and the ",{"tag":140,"children":347},[348],"inputs"," to the price function. When a customer disputes a quote three weeks later, you must be able to recompute it, or at least explain which rules and prices applied. Without this, \"automated pricing\" becomes a support problem.",{"type":133,"content":351},[352],"Two practical points. First, decide early whether the configurator shows a binding price or an indicative one; if some items need manual quoting (special tolerances, large quantities), model that as an explicit outcome (\"request a quote\") rather than a missing price. Second, keep rounding, currency and tax rules in one place, otherwise the configurator, cart and invoice will disagree by cents and you will spend days finding out why.",{"type":150,"level":151,"id":108,"text":109},{"type":133,"content":355},[356,357,360],"On the front end I would build the configurator as a ",{"tag":140,"children":358},[359],"guided dialogue over the server",", not as a client-side state machine that knows the rules. Each step sends the current selection to the configuration service and renders the options that come back, with disabled options carrying a reason. Nuxt fits well because server routes (Nitro) can act as a backend-for-frontend: the browser talks to your own endpoints, which in turn call the configuration service and the commerce API with credentials that never reach the client.",{"type":298,"ordered":299,"items":362},[363,368,373,378],[364,367],{"tag":140,"children":365},[366],"Selection in the URL."," Encode the selection in the query string so a configuration can be shared, bookmarked and restored. It also makes testing easy: a URL is a test case.",[369,372],{"tag":140,"children":370},[371],"Server-driven steps."," The server returns the next step, the valid options and the reasons for disabled ones. Adding a parameter becomes a data change, not a release.",[374,377],{"tag":140,"children":375},[376],"Optimistic preview, authoritative result."," Show cheap local feedback instantly, but treat the server response as the truth and reconcile.",[379,382],{"tag":140,"children":380},[381],"Hand-off through the commerce API."," At the end, call the BFF, which validates once more and then writes the configuration to the cart or quote through the platform API (for Spryker, the Glue API).",{"type":133,"content":384},[385,386,390,391,395,396,400],"If you also want an AI assistant that helps buyers describe what they need (\"a plate for a mould, hardened, 300 mm\"), let it propose a selection and then run that selection through the same service. The model suggests; the rules decide. For the agent side of this, see ",{"tag":144,"to":387,"children":388},"\u002Fblog\u002Fagentic-commerce-protocols-ucp-acp-guide",[389],"agentic commerce protocols",", and for exposing the flow to browser agents, ",{"tag":144,"to":392,"children":393},"\u002Fblog\u002Fwebmcp-agent-ready-website-guide",[394],"WebMCP",". If you ship an assistant, evaluate it like any other product feature, as in ",{"tag":144,"to":397,"children":398},"\u002Fblog\u002Fllm-evals-for-product-features",[399],"LLM evals for product features",".",{"type":150,"level":151,"id":111,"text":112},{"type":133,"content":403},[404,405,408],"Searches for \"Produktkonfigurator TYPO3\" usually come from a company that already runs TYPO3 for its marketing site and wants the configurator inside it. The clean pattern is a division of labour: ",{"tag":140,"children":406},[407],"the CMS owns content, navigation, landing pages and SEO; the configurator owns configuration",". TYPO3 renders the page and mounts the configurator, either as an embedded Nuxt app or as an extension plugin that calls the same configuration service.",{"type":133,"content":410},[411],"What TYPO3 should pass in is context only: the language, the customer group, the product family identifier. What it should not do is hold rules or prices. The moment a rule lives in a content element or a TypoScript condition, product managers cannot test it and developers cannot find it. The same applies to any other CMS, and to the choice between embedding and a fully headless front end: the more of the page the configurator owns, the more consistent the experience, but the more you give up the CMS preview workflow your editors like.",{"type":150,"level":151,"id":114,"text":115},{"type":133,"content":414},[415,416,400],"A catalogue of 100,000+ articles changes how you think about performance, because there is no list to render. The principle is simple: ",{"tag":140,"children":417},[418],"never ship the variant list to the browser; ask the server for what is valid next",{"type":298,"ordered":299,"items":420},[421,426,431,436,441,450],[422,425],{"tag":140,"children":423},[424],"Model the product, not the variants."," Attributes plus rules describe the space; variants are materialised only when needed (the article number, the price).",[427,430],{"tag":140,"children":428},[429],"Index for lookup."," Searchable attributes go into a search engine so that \"which options remain?\" is an index query, not a table scan.",[432,435],{"tag":140,"children":433},[434],"Cache by selection."," Option lookups for the same selection and context are identical; cache them at the edge or in the service, and key the cache by rule-set and price-list version.",[437,440],{"tag":140,"children":438},[439],"Keep payloads small."," Return only the next step, not the whole tree, and compress it.",[442,445,446,449],{"tag":140,"children":443},[444],"Virtualise what is long."," If a step does need a long list (hundreds of diameters, for example), render only the visible window: ",{"tag":183,"href":45,"children":447},[448],"virtualisation"," recycles DOM nodes that leave the viewport so the number of rendered elements follows the window, not the data.",[451,454],{"tag":140,"children":452},[453],"Measure the whole dialogue."," The metric is \"time from click to updated, valid options\", p95, including the ERP price call. Budget it and test it with real selections.",{"type":150,"level":151,"id":117,"text":118},{"type":133,"content":457},[458,459,462],"Everything the browser enforces, the server must enforce again. The ",{"tag":183,"href":42,"children":460},[461],"OWASP Input Validation Cheat Sheet"," is explicit that input validation must be implemented on the server because client-side validation is easily bypassed, and it favours allow-lists: define exactly what is permitted. For a configurator that means the server accepts only selections that are valid under the current rule set, and recomputes derived values and price instead of trusting any it receives.",{"type":133,"content":464},[465,466,469],"Treat a configuration as an ",{"tag":140,"children":467},[468],"immutable, versioned document",". A compact shape works well:",{"type":298,"ordered":299,"items":471},[472,477,482,487],[473,476],{"tag":140,"children":474},[475],"Identity:"," a configuration id, product family, created-by customer and timestamp.",[478,481],{"tag":140,"children":479},[480],"Selection:"," the chosen parameter values, nothing derived.",[483,486],{"tag":140,"children":484},[485],"Versions:"," rule-set version and price-list version used at creation.",[488,491],{"tag":140,"children":489},[490],"Result:"," derived values, price breakdown, and the article number or a \"needs manual review\" flag.",{"type":133,"content":493},[494,495,498],"Saving then means writing that document; ",{"tag":140,"children":496},[497],"quoting"," means freezing it, with a validity period, and linking it to the quote. When a quote is accepted, re-validate against the current rules before ordering: if a rule changed in the meantime, the buyer should hear about it before production does. Whether you keep the document in the configuration service or on the commerce platform is secondary to keeping it complete and replayable.",{"type":150,"level":151,"id":120,"text":121},{"type":133,"content":501},[502],"A configurator is a dense, dynamic form, which is where accessibility usually breaks. The practical reason is simple: a buyer who cannot operate your configurator cannot order. These are the checks I apply, mapped to WCAG 2.2:",{"type":298,"ordered":299,"items":504},[505,514,523,531,540],[506,509,510,513],{"tag":140,"children":507},[508],"Say what is wrong, in text."," ",{"tag":183,"href":48,"children":511},[512],"Success criterion 3.3.1"," requires that an input error is identified and described in text. \"This diameter is not available with hardened steel\" beats a red border.",[515,518,519,522],{"tag":140,"children":516},[517],"Announce changes without stealing focus."," When the price or the option list updates, ",{"tag":183,"href":51,"children":520},[521],"4.1.3 Status Messages"," expects the change to be programmatically determinable, for example with role=\"status\" for updates and role=\"alert\" for errors, so screen readers announce it without moving focus.",[524,509,527,530],{"tag":140,"children":525},[526],"Big enough targets.",{"tag":183,"href":54,"children":528},[529],"2.5.8"," sets a minimum of 24 by 24 CSS pixels for pointer targets, with exceptions; swatches and small steppers are where configurators fail.",[532,535,536,539],{"tag":140,"children":533},[534],"Use proven patterns."," Prefer native radio buttons, selects and number inputs; where you need a searchable option list, follow the ",{"tag":183,"href":57,"children":537},[538],"ARIA combobox pattern"," including its keyboard support.",[541,544],{"tag":140,"children":542},[543],"Disabled options need reasons."," An option that is simply greyed out is a dead end for a screen-reader user; expose the reason in text.",{"type":133,"content":546},[547],"Automated checkers find only part of this. Run the complete flow with a keyboard and at least one screen reader before you call it done.",{"type":150,"level":151,"id":123,"text":124},{"type":133,"content":550},[551],"Before a configurator goes live, I would want a yes to each of these:",{"type":298,"ordered":553,"items":554},true,[555,557,559,561,563,565,567,569],[556],"Every rule is data with an owner, a version and a unit test.",[558],"Rules come from one source (usually the PIM), and publishing a rule set is a reviewed step.",[560],"Prices are computed on the server from a versioned price list; the browser never decides a price.",[562],"Add-to-cart and quote re-validate and re-price; a tampered request is rejected.",[564],"Every saved configuration records rule-set version, price-list version and inputs, and can be recomputed.",[566],"Cases that cannot be priced automatically end in an explicit \"request a quote\", not an error.",[568],"The 95th-percentile \"click to valid options\" time is measured against real selections.",[570],"The whole flow works with keyboard and screen reader, and errors and status changes are announced in text.",{"type":133,"content":572},[573],"If you can only do three things first, do the first, third and fourth.",{"type":150,"level":151,"id":126,"text":127},{"type":133,"content":576},[577],"The recurring theme is separation. Rules as data, owned in one place; prices governed by the system that is accountable for them; a thin front end that asks rather than knows; a commerce platform that receives validated results. That is also what makes the configurator stable enough to extend, whether the next step is a new product family, a quote workflow or an assistant on top.",{"type":133,"content":579},[580,581,584,585,589],"The guided configurator I built in Nuxt.js on the Spryker Glue API is listed in ",{"tag":144,"to":145,"children":582},[583],"my references",". If you are planning one and want to talk through the rule engine versus solver decision or the PIM and ERP split, my ",{"tag":144,"to":586,"children":587},"\u002Fexpertise\u002Fb2b-ecommerce-developer",[588],"B2B e-commerce developer page"," has the details.",{"type":150,"level":151,"id":129,"text":130},{"type":298,"ordered":553,"items":592},[593,596,599,602,605,608,611,614,617],[594],{"tag":183,"href":26,"children":595},[33],[597],{"tag":183,"href":36,"children":598},[35],[600],{"tag":183,"href":39,"children":601},[38],[603],{"tag":183,"href":42,"children":604},[41],[606],{"tag":183,"href":45,"children":607},[44],[609],{"tag":183,"href":48,"children":610},[47],[612],{"tag":183,"href":51,"children":613},[50],[615],{"tag":183,"href":54,"children":616},[53],[618],{"tag":183,"href":57,"children":619},[56],[621,706,773,861],{"slug":622,"published":5,"minutes":623,"category":7,"tags":624,"keywords":628,"about":639,"sources":648,"cover":700,"og":701,"expertise":60,"locales":702,"lang":62,"title":703,"description":704,"coverAlt":705},"european-accessibility-act-b2b-shop",13,[625,626,627,10],"European Accessibility Act","BFSG","WCAG 2.2",[629,630,631,632,633,634,635,636,637,638],"European Accessibility Act B2B shop","BFSG B2B Onlineshop","Barrierefreiheit Onlineshop","Spryker barrierefrei","barrierefrei Webshop","BaFG Österreich Webshop","EN 301 549 WCAG 2.2","BFSG Kleinstunternehmen Ausnahme","Pimcore TYPO3 Barrierefreiheit","EAA e-commerce checklist",[640,642,645],{"name":625,"url":641},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEuropean_Accessibility_Act",{"name":643,"url":644},"Web Content Accessibility Guidelines","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWeb_Content_Accessibility_Guidelines",{"name":646,"url":647},"E-commerce","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FE-commerce",[649,652,655,658,661,664,667,670,673,676,679,682,685,688,691,694,697],{"title":650,"url":651},"BFSG section 1: purpose and scope (gesetze-im-internet.de)","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__1.html",{"title":653,"url":654},"BFSG section 2: definitions, consumer, microenterprise, e-commerce services","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__2.html",{"title":656,"url":657},"BFSG section 3: accessibility and the microenterprise exemption","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__3.html",{"title":659,"url":660},"BFSG section 14: duties of the service provider","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__14.html",{"title":662,"url":663},"BFSG section 32: rights of consumers and associations in the administrative procedure","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__32.html",{"title":665,"url":666},"BFSG section 37: fines","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__37.html",{"title":668,"url":669},"BFSG section 38: transitional provisions","https:\u002F\u002Fwww.gesetze-im-internet.de\u002Fbfsg\u002F__38.html",{"title":671,"url":672},"Bundesfachstelle Barrierefreiheit: FAQ on the BFSG (B2B, microenterprises, EN 301 549)","https:\u002F\u002Fwww.bundesfachstelle-barrierefreiheit.de\u002FDE\u002FFachwissen\u002FProdukte-und-Dienstleistungen\u002FBarrierefreiheitsstaerkungsgesetz\u002FFAQ\u002Ffaq_node.html",{"title":674,"url":675},"AccessibleEU: The European accessibility standard EN 301 549 has been updated (7 September 2026)","https:\u002F\u002Faccessible-eu-centre.ec.europa.eu\u002Fcontent-corner\u002Fnews\u002Feuropean-accessibility-standard-en-301-549-has-been-updated-2026-09-07_en",{"title":677,"url":678},"W3C: Web Content Accessibility Guidelines (WCAG) 2.2","https:\u002F\u002Fwww.w3.org\u002FTR\u002FWCAG22\u002F",{"title":680,"url":681},"Deque: Automated testing identifies 57 percent of digital accessibility issues","https:\u002F\u002Fwww.deque.com\u002Fblog\u002Fautomated-testing-study-identifies-57-percent-of-digital-accessibility-issues\u002F",{"title":683,"url":684},"sitebrunch: What is the market surveillance authority for accessibility (MLBF)?","https:\u002F\u002Fwww.sitebrunch.com\u002Fnews\u002Fmlbf-marktueberwachungsstelle-barrierefreiheit",{"title":686,"url":687},"Marcus Herrmann: June 2026, how the MLBF intends to check","https:\u002F\u002Fmarcus-herrmann.com\u002Fblog\u002Fmlbf-verraet-wie-sie-pruefen-will",{"title":689,"url":690},"axes4: BFSG in practice, what has happened since the deadline (2026)","https:\u002F\u002Fwww.axes4.com\u002Fde\u002Fblog\u002Fpost\u002F2026\u002Fbfsg-in-der-praxis-was-sich-seit-dem-stichtag-getan-hat",{"title":692,"url":693},"ODC Legal: BFSG for websites, what companies must check in 2026","https:\u002F\u002Fwww.odclegal.de\u002Fblog\u002Fbfsg-website-pflicht",{"title":695,"url":696},"WKO: Information on the Austrian Barrierefreiheitsgesetz","https:\u002F\u002Fwww.wko.at\u002Fce-kennzeichnung-normen\u002Finformationen-zum-barrierefreiheitsgesetz",{"title":698,"url":699},"Web Crossing: One year of the Barrierefreiheitsgesetz, where online shops must improve","https:\u002F\u002Fwww.web-crossing.com\u002Fnews\u002Fdetail\u002Fein-jahr-barrierefreiheitsgesetz-wo-onlineshops-und-websites-jetzt-nachbessern-muessen\u002F","\u002Fimages\u002Fblog\u002Feuropean-accessibility-act-b2b-shop\u002Fcover.webp","\u002Fimages\u002Fblog\u002Feuropean-accessibility-act-b2b-shop\u002Fog.jpg",[62,63,64],"European Accessibility Act for B2B shops: what Spryker, Pimcore and TYPO3 teams must fix","Does the European Accessibility Act apply to a B2B shop? Scope under BFSG and BaFG, the microenterprise exemption, 2026 enforcement, WCAG 2.2 and a fix plan.","Diagram: a B2B shop fans out to the consumer-scope question under BFSG and BaFG, WCAG 2.2 AA conformity, the accessibility statement and market surveillance.",{"slug":707,"published":5,"minutes":6,"category":7,"tags":708,"keywords":713,"about":724,"sources":733,"cover":767,"og":768,"expertise":60,"locales":769,"lang":62,"title":770,"description":771,"coverAlt":772},"pimcore-erp-delta-sync",[709,710,711,712],"Pimcore","SAP integration","PIM ERP sync","Symfony Messenger",[714,715,716,717,718,719,720,721,722,723],"Pimcore ERP delta sync","Pimcore developer","Pimcore SAP integration","PIM ERP Schnittstelle","Pimcore SAP Schnittstelle","PIM ERP sync best practices","Pimcore Symfony Messenger","delta sync vs full sync","idempotent import Pimcore","Pimcore Infor integration",[725,727,730],{"name":709,"url":726},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPimcore",{"name":728,"url":729},"SAP ERP","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSAP_ERP",{"name":731,"url":732},"Product information management","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct_information_management",[734,737,740,743,746,749,752,755,758,761,764],{"title":735,"url":736},"Pimcore docs: Symfony Messenger","https:\u002F\u002Fdocs.pimcore.com\u002Fplatform\u002FGetting_Started\u002FInstallation\u002FAdvanced_Installation_Topics\u002FSymfony_Messenger\u002F",{"title":738,"url":739},"Pimcore docs: Platform Versions","https:\u002F\u002Fdocs.pimcore.com\u002Fplatform\u002FPimcore_Platform\u002FPlatform_Versions\u002F",{"title":741,"url":742},"Pimcore docs: Pimcore Editions","https:\u002F\u002Fdocs.pimcore.com\u002Fplatform\u002FPimcore_Platform\u002FPimcore_Editions\u002F",{"title":744,"url":745},"Pimcore docs: Data Importer","https:\u002F\u002Fdocs.pimcore.com\u002Fplatform\u002FData_Importer\u002F",{"title":747,"url":748},"Pimcore on GitHub: Release 2026.3.0","https:\u002F\u002Fgithub.com\u002Fpimcore\u002Fpimcore\u002Freleases\u002Ftag\u002Fv2026.3.0",{"title":750,"url":751},"Symfony docs: Messenger, Sync & Queued Message Handling","https:\u002F\u002Fsymfony.com\u002Fdoc\u002Fcurrent\u002Fmessenger.html",{"title":753,"url":754},"SAP Library: Change Pointer (Master Data Distribution)","https:\u002F\u002Fhelp.sap.com\u002Fdoc\u002Fsaphelp_nw73ehp1\u002F7.31.19\u002Fen-us\u002F4a\u002Fbb1e253536478be10000000a421937\u002Fcontent.htm?no_cache=true",{"title":756,"url":757},"SAP Library: Change Pointer (IDoc Interface\u002FALE)","https:\u002F\u002Fhelp.sap.com\u002Fdoc\u002Fsaphelp_em700_ehp01\u002F7.0.1\u002Fen-US\u002F12\u002F83e03c19758e71e10000000a114084\u002Fcontent.htm?no_cache=true",{"title":759,"url":760},"Infor docs: Infor ION and M3 BODs","https:\u002F\u002Fdocs.infor.com\u002Fm3cs\u002F10.2.3\u002Fen-us\u002Fm3csbom\u002Ffabsog\u002Fgoi1495809108914.html",{"title":762,"url":763},"Infor Developer Portal: Integration with ION","https:\u002F\u002Fdeveloper.infor.com\u002Ftutorials\u002Fintegration-with-ion",{"title":765,"url":766},"Debezium: open source distributed platform for change data capture","https:\u002F\u002Fdebezium.io\u002F","\u002Fimages\u002Fblog\u002Fpimcore-erp-delta-sync\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fpimcore-erp-delta-sync\u002Fog.jpg",[62,63,64],"Pimcore ERP delta sync: syncing product data between SAP or Infor, PIM and shop","How to sync product data between SAP or Infor, Pimcore and a shop: delta vs full sync, change detection, idempotent imports, Messenger queues, ownership and replays.","Diagram: product data flows from an SAP or Infor ERP through a delta extract and a message queue into Pimcore, where quality gates run, and on to the shop.",{"slug":774,"published":5,"minutes":623,"category":7,"tags":775,"keywords":778,"about":789,"sources":796,"cover":854,"og":855,"expertise":856,"locales":857,"lang":62,"title":858,"description":859,"coverAlt":860},"nuxt-core-web-vitals-performance",[776,11,777,646],"Core Web Vitals","Performance",[779,780,781,782,783,784,785,786,787,788],"Core Web Vitals Nuxt","Nuxt performance optimization","improve LCP Nuxt","INP Nuxt hydration","fix CLS Vue","Nuxt lazy hydration","Nuxt Image LCP","Nuxt Scripts third-party scripts","Nuxt prerender vs SSR vs ISR","Core Web Vitals e-commerce",[790,792,793],{"name":776,"url":791},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCore_Web_Vitals",{"name":11,"url":30},{"name":794,"url":795},"Vue.js","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FVue.js",[797,800,803,806,809,812,815,818,821,824,827,830,833,836,839,842,845,848,851],{"title":798,"url":799},"web.dev: Web Vitals","https:\u002F\u002Fweb.dev\u002Farticles\u002Fvitals",{"title":801,"url":802},"web.dev: Largest Contentful Paint (LCP)","https:\u002F\u002Fweb.dev\u002Farticles\u002Flcp",{"title":804,"url":805},"web.dev: Optimize Largest Contentful Paint","https:\u002F\u002Fweb.dev\u002Farticles\u002Foptimize-lcp",{"title":807,"url":808},"web.dev: Interaction to Next Paint (INP)","https:\u002F\u002Fweb.dev\u002Farticles\u002Finp",{"title":810,"url":811},"web.dev: Optimize Cumulative Layout Shift","https:\u002F\u002Fweb.dev\u002Farticles\u002Foptimize-cls",{"title":813,"url":814},"web.dev: Optimize long tasks","https:\u002F\u002Fweb.dev\u002Farticles\u002Foptimize-long-tasks",{"title":816,"url":817},"Chrome for Developers: Chrome UX Report (CrUX)","https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fcrux",{"title":819,"url":820},"Chrome for Developers: CrUX methodology","https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fcrux\u002Fmethodology",{"title":822,"url":823},"GitHub: GoogleChrome\u002Fweb-vitals","https:\u002F\u002Fgithub.com\u002FGoogleChrome\u002Fweb-vitals",{"title":825,"url":826},"Nuxt docs: Rendering modes","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fconcepts\u002Frendering",{"title":828,"url":829},"Nuxt docs: Components (Lazy prefix, delayed hydration, client components)","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fdirectory-structure\u002Fapp\u002Fcomponents",{"title":831,"url":832},"Nuxt docs: Experimental features (lazyHydration, payloadExtraction)","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fgoing-further\u002Fexperimental-features",{"title":834,"url":835},"Nuxt docs: Data fetching (pick, transform, lazy)","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fgetting-started\u002Fdata-fetching",{"title":837,"url":838},"Nuxt blog: Nuxt 4.5","https:\u002F\u002Fnuxt.com\u002Fblog\u002Fv4-5",{"title":840,"url":841},"GitHub: Nuxt releases","https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fnuxt\u002Freleases",{"title":843,"url":844},"Nuxt Image: NuxtImg","https:\u002F\u002Fimage.nuxt.com\u002Fusage\u002Fnuxt-img",{"title":846,"url":847},"Nuxt Fonts module","https:\u002F\u002Fnuxt.com\u002Fmodules\u002Ffonts",{"title":849,"url":850},"Nuxt Scripts: Getting started","https:\u002F\u002Fscripts.nuxt.com\u002Fdocs\u002Fgetting-started",{"title":852,"url":853},"Nuxt Scripts: Script triggers","https:\u002F\u002Fscripts.nuxt.com\u002Fdocs\u002Fguides\u002Fscript-triggers","\u002Fimages\u002Fblog\u002Fnuxt-core-web-vitals-performance\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fnuxt-core-web-vitals-performance\u002Fog.jpg","vue-nuxt-developer",[62,63,64],"Core Web Vitals for Nuxt sites and shops: fixing LCP, INP and CLS","How to fix LCP, INP and CLS in Nuxt 4 sites and shops: images, hydration, fonts, third-party scripts, prerender vs SSR vs ISR, and measuring real users.","Diagram: a Nuxt page load from server HTML through hero image, hydration and interaction, with the LCP, INP and CLS thresholds marked on the stages.",{"slug":862,"published":863,"minutes":864,"category":7,"tags":865,"keywords":871,"about":880,"sources":889,"cover":902,"og":903,"expertise":856,"locales":904,"lang":62,"title":905,"description":906,"coverAlt":907},"home-assistant-ev-charging-energy-manager","2026-09-29",9,[866,867,868,869,870],"Home Assistant","EPEX Spot","Energy prices","Tesla","Energy management",[872,873,874,875,876,877,878,879],"EPEX Austria prices","spot tariff Austria EV charging","Home Assistant EV charging","cheapest hours charging","day-ahead price Austria","negative electricity prices Austria","Tesla charging spot prices","load management 3 phase",[881,884,886],{"name":882,"url":883},"EPEX SPOT","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEPEX_SPOT",{"name":866,"url":885},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHome_Assistant",{"name":887,"url":888},"Load management","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLoad_management",[890,893,896,899],{"title":891,"url":892},"aWATTar Austria: market data API","https:\u002F\u002Fwww.awattar.at\u002Fservices\u002Fapi",{"title":894,"url":895},"EPEX SPOT: market results","https:\u002F\u002Fwww.epexspot.com\u002Fen\u002Fmarket-results",{"title":897,"url":898},"Home Assistant developer docs: apps","https:\u002F\u002Fdevelopers.home-assistant.io\u002Fdocs\u002Fapps\u002F",{"title":900,"url":901},"Home Assistant: Tesla Fleet integration","https:\u002F\u002Fwww.home-assistant.io\u002Fintegrations\u002Ftesla_fleet\u002F","\u002Fimages\u002Fblog\u002Fhome-assistant-ev-charging-energy-manager\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fhome-assistant-ev-charging-energy-manager\u002Fog.jpg",[62,63,64],"Charging on EPEX Austria prices: what my Home Assistant app saves","A year of hourly EPEX prices for Austria, replayed for a Tesla and a water boiler: cheapest-hour charging costs 6.5 instead of 18.9 ct\u002FkWh, close to €590 a year, without blowing a 20 A fuse.","Cover: three bars comparing 18.9, 13.1 and 6.5 ct\u002FkWh for charging at 18:00, overnight and in the cheapest hours.",1791009037053]