The essential news about content management systems and mobile technology. Powered by Perfect Publisher and XT Search for Algolia.
The News Site publishes posts to the following channels: Facebook, Instagram, Twitter, Telegram, Web Push, Bluesky, and Blogger.
Picture this: You’ve built a beautiful design system using Web Components. Your components are framework-agnostic, encapsulated, and performant. Life is good… until someone asks, “But does it work with SSR?”
Record scratch. Freeze frame.
That’s where our story begins.
Before we dive into the trenches, let’s set the stage. Server-Side Rendering (SSR) is the golden child of modern web development. It promises:
Meanwhile, Web Components offer their own slice of paradise:
So naturally, combining these two should create the ultimate web development utopia, right?
Narrator: It did not.
Here’s the thing about Web Components — they’re inherently client-side creatures. They rely on browser APIs like customElements.define() and the Shadow DOM, which don’t exist in Node.js land. It’s like trying to teach a fish to climb a tree.
The main challenges we faced:
Web Components love their browser APIs (window, document, localStorage). Servers? Not so much. Running these components on the server is like asking your coffee machine to make tea — technically possible, but requires some creative engineering.
The Shadow DOM is fantastic for encapsulation, but servers don’t speak Shadow DOM. We needed to somehow replicate this encapsulation server-side, which led us down the rabbit hole of Declarative Shadow DOM.
Hydration — the process where client-side JavaScript takes over server-rendered HTML — becomes particularly tricky with Web Components. It’s like performing a magic trick where you swap a cardboard cutout with a real person without anyone noticing.
Lit approached SSR with elegance. Their solution renders components to static HTML using a low-level library that doesn’t fully emulate the DOM. Here’s what a Lit component looks like when server-rendered:
<simple-greeter name="Friend">
<template shadowroot="open" shadowrootmode="open">
<style>/* component styles */</style>
<div>
<h1>Hello, <span>Friend</span>!</h1>
<p>Count: 0</p>
<button>++</button>
</div>
</template>
</simple-greeter>
Clean, declarative, and it works. But here’s the catch — there aren’t many real-world examples of this being used in large-scale design systems. It’s like having a sports car that looks amazing but you’re not quite sure how it handles on actual roads.
At Stencil, we decided to throw everything at the problem. Our toolkit includes:
Here’s the twist: we ended up with TWO different approaches because one size definitely doesn’t fit all.
Our compiler-based approach, implemented in the @stencil/ssr package, works like a skilled translator at a United Nations meeting. It operates as a Vite or Webpack plugin, intercepting your code at build time and performing some serious AST (Abstract Syntax Tree) surgery.
Here’s the magic: when you write this innocent-looking React component:
import { MyComponent } from 'component-library-react'
function App() {
return (
<div>
<h1>SSR Test</h1>
<MyComponent first="Stencil" middleName="'Don't call me a framework'" last="JS" />
</div>
)
}
The plugin receives it as a pile of jsx calls after transformation:
import { jsxDEV } from "react/jsx-dev-runtime";
/* @__PURE__ */ jsxDEV(MyComponent, {
first: "Stencil",
middleName: "'Don't call me a framework'",
last: "JS"
}, void 0, false, ...this)
Now comes the clever part. The plugin:
The result? Your MyComponent becomes MyComponent$0:
const MyComponent$0 = ({ children, ...props }) => {
return <my-component class="hydrated sc-my-component-h"
first="Stencil"
last="JS"
middle-name="'Don't call me a framework'"
s-id="1"
suppressHydrationWarning={true}
{...props}>
<template shadowrootmode="open" suppressHydrationWarning={true}
dangerouslySetInnerHTML={{
__html: `<style>:host{display:block;color:green}</style>
<div c-id="1.0.0.0" class="sc-my-component">
<!--t.1.1.1.0-->
Hello, World! I'm Stencil 'Don't call me a framework' JS
</div>`
}}></template>
{children}
</my-component>
}
For Next.js specifically, we go even fancier with dynamic imports:
const MyComponent$0Instance = dynamic(
() => componentImport.then(mod => mod.MyComponent),
{
ssr: false,
loading: () => <MyComponent$0 {...props}>{children}</MyComponent$0>
}
)
The Pros:
The Cons:
The runtime approach is like having a personal chef instead of meal prep. When Next.js hits a Stencil component during server rendering, we spring into action in real-time.
This approach leverages Next.js Server Components, which support async operations. Here’s how it works:
The implementation looks something like this:
// Server Component Wrapper
async function MyComponentSSR({ children, ...props }) {
// Serialize all props for Stencil
const serializedProps = Object.entries(props)
.map(([key, value]) => `${key}="${serializeProperty(value)}"`)
.join(' ');
// Attempt to render children to string
let childrenHtml = '';
try {
childrenHtml = ReactDOMServer.renderToString(children);
} catch (e) {
// Handle nested server components gracefully
console.warn('Complex children detected, using fallback');
}
// Render the component with Stencil's SSR
const { html } = await renderToString(
`<my-component ${serializedProps}>${childrenHtml}</my-component>`,
{ prettyHtml: true }
);
// Parse back to React
return parseHtmlToReact(html, {
suppressHydrationWarning: true
});
}
The Pros:
The Cons:
So which approach should you use? Here’s our battle-tested decision tree:
In practice, we’ve seen teams start with the compiler approach for its simplicity and broader compatibility, then selectively use the runtime approach for specific components that need its advanced features.
Remember how we embed styles in each component’s Declarative Shadow DOM? Well, imagine a button component used 50 times on a page. That’s the same styles repeated 50 times. Your HTML document becomes chonkier than a sumo wrestler.
Our workaround? Stencil’s “scoped components” — fake web components that transform into real ones on the client. It’s like shipping IKEA furniture instead of assembled pieces.
Complex components that render differently based on conditions or child nodes? That’s where things get spicy. We introduced build flags to help:
render() {
if (Build.isServer) {
return <div>loading...</div>
}
return <ul>{/* actual content */}</ul>
}
It’s not elegant, but it works. Sometimes you need duct tape to hold things together.
So, are Web Components ready for prime-time SSR? Well… it’s complicated.
The good news: We’ve made it work. Stencil now supports SSR in React and Vue environments. You can build design systems with Web Components and render them on the server.
The reality check: It’s not seamless. Even lit.dev uses server-rendered Web Components sparingly. The overhead, complexity, and edge cases mean that for many applications, traditional framework components still make more sense.
Web Components shine in environments where SSR doesn’t matter — Chrome’s internal pages, Electron apps, VS Code extensions. But for your typical Next.js e-commerce site? The jury’s still out.
The inability to elegantly handle SSR remains one of the biggest barriers to Web Components adoption. We’re making progress, but there’s still a mountain to climb.
The web platform is evolving. Proposals like Declarative Custom Elements could eliminate many current pain points. Until then, we’ll keep pushing, experimenting, and occasionally pulling our hair out.
Because that’s what we do. We’re developers. We solve problems, even when those problems involve making fish climb trees.
Thanks for joining me on this journey through the SSR wilderness. May your hydration errors be few and your bundle sizes small.
About the Author: A Stencil framework developer who has spent way too much time thinking about Shadow DOMs and server environments. When not wrestling with SSR, can be found explaining why Web Components aren’t dead yet.
The post The Quest for SSR with Web Components: A Stencil Developer’s Journey appeared first on Ionic Blog.
Read more https://ionic.io/blog/the-quest-for-ssr-with-web-components-a-stencil-developers-journey
Bootstrap v5.3.7 was just released with some follow-up fixes from our migration to Astro, plus a handful of small fixes. We expect to have another patch release shortly due to at least one recent regression, so stay tuned for that.
In the mean time, here are some highlights!
<head>
content generated by
the “Download examples” buttonintegrity
and crossorigin
attributes in introduction pageplaceholder
usage
description'none'
values in the
box-shadow
Sass mixin for cleaner outputtrigger: "hover
click"
configurationRead the GitHub v5.3.7 changelog for a full list of changes (including a ton of documentation and dependency updates) in this release.
Read more https://blog.getbootstrap.com/2025/06/17/bootstrap-5-3-7/
We’re thrilled to share the latest release of Ionic Framework, packed with exciting new features and improvements. This release brings a new Input OTP component, a new Datetime property to show days from the previous and next months, and a fresh update to Stencil.
Let’s take a closer look at what’s new in 8.6
We’ve introduced a new ion-input-otp
component that
provides a modern and accessible solution for one-time password
inputs. It’s perfect for use cases like verification codes,
security PINs, and other numeric or alphanumeric input fields.
Intelligent Input Handling
Flexible Styling
If you’re building signup flows, logins, or two-factor screens, this component helps you deliver a better, more seamless user experience.
See the Input OTP documentation for more information.
The Datetime component now supports the
showAdjacentDays
property, which displays days from
adjacent months within the calendar view. This enhancement improves
usability by making it easier to select dates near month
boundaries. When showAdjacentDays
is enabled, users
can select adjacent days (unless the day is disabled) and the
calendar will automatically navigate to the appropriate month.
showAdjacentDays = true |
showAdjacentDays = false |
See the Datetime documentation for more information.
We’ve added iOS 18 haptic feedback to the
ion-toggle
component, providing users with tactile
feedback when toggling switches on supported devices. This
enhancement brings a more native and responsive feel to your
apps.
Stencil has been updated from v4.20
to
v4.33
, bringing in the latest features, enhancements,
and bug fixes from the compiler that powers Ionic Framework.
Key Fixes & Improvements
ion-item
or ion-select. (57e7e58)declarative-shadow-dom
or scoped
rendering modes in SSR. (26e4aa3)For a complete list of updates, check out the Stencil Changelog.
Although this release has been thoroughly tested in Ionic Framework and related projects, there’s always a chance of regressions. If you encounter any issues related to this Stencil update, please open a new issue.
This release also brings attention to several important fixes from recent patches, alongside updates included in this release:
ion-input
or ion-textarea
. For example,
if a button is placed in the end
slot, pressing
Tab will now correctly move focus to the button and
beyond. (2dea607)click
event only
once when clicking padded space and emit the correct element.
(7a9d138)onClick
events when clicking labels in several components. (7d639b0)backdrop-no-scroll
class when toast is presented.
(7f9df7a)
Thanks to tobiloeb for this
contribution! We’re currently investigating support for the latest version of React Router and will share more details soon. We’re also organizing the key issues we want to address in the next major Ionic Framework release.
This summer, we’re excited to welcome interns from Madison College to the project! They’re already diving into Ionic Framework issues, and we’re looking forward to their contributions.
We hope you’ll try out the latest updates and let us know how
they’re working for you. Your feedback helps shape the future of
Ionic. Thank you for being an essential part of the Ionic community
and for your continued support. Stay tuned for more updates soon!
The post Announcing Ionic 8.6 appeared first on Ionic Blog.
Read more https://ionic.io/blog/announcing-ionic-8-6
This is the second blog post in a multipart series. While it’s not necessary to read the first part it will definitely add some much needed context: Cross-platform AR/VR With the Web: WebXR with A-Frame, Angular, and Capacitor (Part I)
Last year I covered a topic about incorporating WebXR into your Capacitor projects to create cross-platform Virtual Reality experiences. While this previous blog post was a great starting point for using WebXR it really only scratched the surface of what’s possible with A-Frame. Recently, I revisited this project to create a low-code AR/VR video tutorial on the OutSystems’ YouTube channel for OutSystems Developer Cloud (ODC) and it inspired me to expand on this original blog post to show off how it can be done in Capacitor.
For the second part of this blog post I want to accomplish three things:
The reason why I want to do these tasks is to provide more depth so you have flexibility with your version of the project. In the original blog post I demonstrated how to create a controlled version of the project that fit within the lines of the tutorial and this time I want to really explore how you would go on to create that experience yourself. This means using custom animated 3D models that you might have created in Blender, showing you libraries that enhance your XR projects that I didn’t cover, and showing you how to overcome the challenges you’ll face with native device functionality when you run this AR project in mobile.
To accomplish this I am going to use a 3D Neo model that I custom animated in Blender and use A-Frame, A-Frame Extras, AR.js, and Angular to build a project around Neo. Then we’ll use Capacitor to make our project cross-platform and run it on an iOS device with the proper camera permissions.
Let’s jump into it!
ionic start webAR blank --type=angular
2. Install A-Frame and A-Frame Extras into your project
npm install --save aframe
npm install --save aframe-extras
Note: The A-Frame Extras library lets us play our animations that are attached to our model
3. Import A-Frame and A-Frame Extras in polyfills.ts under Browser Polyfills
* BROWSER POLYFILLS
*/
import 'aframe';
import 'aframe-extras';
/**
4. Download aframe-ar-nft.js and add it to the assets folder
https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js
Note: When I tried using NPM for this library it required a separate import for Three.js whereas downloading it did not. That’s why we’re downloading it.
5. Add aframe-ar-nft.js to your angular.json file scripts
"scripts": [
"src/assets/aframe-ar-nft.js"
]
A really important aspect of this project is the 3D model that we’ll be using in our Ionic-Angular project, the software we’re using to build/animate our characters, and where we plan to store our character:
In this case, I am using Blender to animate a 3D Neo provided by the OutSystems design team and have named this character animation Waving since that is what Neo is doing in the animation. While I don’t want to get into the details of how to animate the character (since this would quickly become an intermediate Blender tutorial) there’s many resources on how to animate 3D models online. For this tutorial, you just need to export it from your 3D animation software in a .glb or .gltf format then upload it someplace where it’ll be accessible like an S3 bucket in AWS to reference it in our HTML.
Once you have exported your 3D model and it’s in a place where it can be accessed by your app then you can start referencing it in your app. If you don’t have a 3D model available or want to work with a model that’s animated then definitely check out places like TurboSquid that have tons of assets.
Next we’ll need to modify our project to recognize the custom elements that are a part of A-Frame then download the marker we’ll be using as a reference for our app to place the 3D model on:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
imports: [IonHeader, IonToolbar, IonTitle, IonContent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HomePage {
constructor() {}
}
Note: If you don’t define CUSTOM_ELEMENTS_SCHEMA your project will throw an error
2. Add AR HTML to home.page.html
<a-scene embedded arjs>
<a-marker preset='hiro'>
<a-entity
position='0 0 0'
scale='0.5 0.5 0.5'
gltf-model='Place .glb URL here'
animation-mixer='clip: Waving'
></a-entity>
</a-marker>
<a-entity camera></a-entity>
</a-scene>
3. Download and print the Hiro marker
If you checked out my previous blog post on WebXR this should look familiar but there are a few differences: In our scene we’re letting A-Frame and AR.js know that this is an embedded AR experience with embedded arjs and adding an <a-marker> tag defining that we’ll be placing our object on a Hiro marker. Then we are using an <a-entity> tag because we’re moving away from preset shapes to import a custom 3D model and adding animation-mixer so the aframe-extras library knows that the attached animation clip Waving needs to be played. Finally, we are adding another <a-entity> tag defining it as a camera because the camera in our scene operates as its own entity.
4. (Optional) Test the application to make sure the experience is working correctly with a camera
ionic capacitor build
ionic serve
For our project to run on mobile we’ll need to add a iOS as a platform to our Capacitor project and configure the permissions so we have access to the camera:
ionic capacitor add ios
ionic capacitor build
ionic capacitor sync
//Works best if you run it on a physical device
ionic capacitor run ios
Note: You should be able to add Android and run it on a WebXR capable Android device. I don’t have an Android device to test this project so I’m excluding it as a platform.
2. Adjust permissions in info.plist in XCode
//info.plist - iOS
<key>NSCameraUsageDescription</key>
<string>Allow camera</string>
With that complete you can now run your project in XCode on an iPhone! Given the experimental nature of this library with Capacitor it’s possible to run into sizing issues with orientation and you will likely need to tweak the HTML to fit your particular use case. In my experience, this works best in landscape mode and it minimizes having to code around the interface for it to scale in multiple orientations. It also reduces how much you need to scale and/or position your 3D model in your scene to get it to fit on your Hiro marker.
Overall, this project was a lot of fun but there’s still more projects you can do with A-Frame, AR.js, and Capacitor! Be sure to check out some of the other features available and feel free to share your projects with us on Discord, X, or Bluesky!
The post Cross-platform AR/VR With the Web: WebXR with A-Frame, Angular, and Capacitor (Part II) appeared first on Ionic Blog.
In the last few weeks, a handful of new Bootstrap Icons releases have gone out. Here’s a recap of what’s new in our v1.12.x and v1.13.x releases so far.
v1.12.0 added a single icon, mostly because people wouldn’t stop asking for it haha, and v1.12.1 added a page to the docs for it. That was all for Bluesky.
New in v1.13.0 are several icons, some guidance around how to use Sass files with Vite, and a few little bug fixes to some fill rules. v1.13.1 was a hotfix to resolve an issue with search on our docs.
Read more https://blog.getbootstrap.com/2025/05/09/bootstrap-icons-1-12-1-13/