@authhero/widget 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,136 @@ const html = await renderToString(`
57
57
  `);
58
58
  ```
59
59
 
60
+ ## SSR and Hydration
61
+
62
+ The widget is built with StencilJS and includes full server-side rendering (SSR) support with client-side hydration. This enables fast initial page loads while maintaining full interactivity.
63
+
64
+ ### How It Works
65
+
66
+ 1. **Server-Side Rendering**: The server renders the widget to HTML using `renderToString()` from `@authhero/widget/hydrate`. This produces static HTML with Declarative Shadow DOM that displays instantly without JavaScript.
67
+
68
+ 2. **Client-Side Hydration**: When the browser loads the page, the widget's ESM bundle "hydrates" the server-rendered HTML, attaching event listeners and enabling interactivity without re-rendering the content.
69
+
70
+ 3. **Progressive Enhancement**: Users see content immediately (even with JavaScript disabled), and interactivity is added once the JavaScript loads.
71
+
72
+ ### Rendering Options
73
+
74
+ ```typescript
75
+ import { renderToString } from "@authhero/widget/hydrate";
76
+
77
+ const result = await renderToString(
78
+ `<authhero-widget screen='${JSON.stringify(screen)}'></authhero-widget>`,
79
+ {
80
+ // Return only the widget HTML, not a full document
81
+ fullDocument: false,
82
+
83
+ // Use Declarative Shadow DOM for SSR (recommended)
84
+ serializeShadowRoot: "declarative-shadow-dom",
85
+
86
+ // Optional: Remove unused styles for smaller payload
87
+ removeUnusedStyles: true,
88
+
89
+ // Optional: Format HTML for debugging
90
+ prettyHtml: false,
91
+ }
92
+ );
93
+
94
+ // result.html contains the rendered HTML string
95
+ // result.diagnostics contains any warnings or errors
96
+ const widgetHtml = result.html;
97
+ ```
98
+
99
+ ### Shadow DOM Serialization Modes
100
+
101
+ The `serializeShadowRoot` option controls how shadow DOM is rendered:
102
+
103
+ - **`"declarative-shadow-dom"`** (default): Uses the browser's native Declarative Shadow DOM feature. This is the most efficient option as the shadow DOM is part of the initial HTML.
104
+
105
+ - **`"scoped"`**: Renders content as scoped CSS without shadow DOM. The shadow DOM is then created during client-side hydration.
106
+
107
+ - **Mixed mode**: You can mix both approaches for different components:
108
+ ```typescript
109
+ serializeShadowRoot: {
110
+ 'declarative-shadow-dom': ['authhero-widget'],
111
+ scoped: ['some-other-component'],
112
+ default: 'declarative-shadow-dom'
113
+ }
114
+ ```
115
+
116
+ ### Complete SSR Example (Hono)
117
+
118
+ ```typescript
119
+ import { Hono } from "hono";
120
+ import { renderToString } from "@authhero/widget/hydrate";
121
+
122
+ const app = new Hono();
123
+
124
+ app.get("/login", async (c) => {
125
+ // Fetch screen configuration from your backend
126
+ const screen = await getLoginScreen();
127
+ const branding = await getBranding();
128
+
129
+ // Server-side render the widget
130
+ const widgetResult = await renderToString(
131
+ `<authhero-widget
132
+ screen='${JSON.stringify(screen).replace(/'/g, "&#39;")}'
133
+ branding='${JSON.stringify(branding).replace(/'/g, "&#39;")}'
134
+ auto-submit="true"
135
+ auto-navigate="true"
136
+ ></authhero-widget>`,
137
+ {
138
+ fullDocument: false,
139
+ serializeShadowRoot: "declarative-shadow-dom",
140
+ }
141
+ );
142
+
143
+ // Return complete HTML page
144
+ return c.html(`
145
+ <!DOCTYPE html>
146
+ <html>
147
+ <head>
148
+ <meta charset="UTF-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
150
+ <title>Login</title>
151
+ <script type="module" src="/widget/authhero-widget.esm.js"></script>
152
+ </head>
153
+ <body>
154
+ ${widgetResult.html}
155
+ </body>
156
+ </html>
157
+ `);
158
+ });
159
+ ```
160
+
161
+ ### Edge Runtime Compatibility
162
+
163
+ The hydrate module works on edge runtimes like Cloudflare Workers. For compatibility, ensure the global `window` object exists:
164
+
165
+ ```typescript
166
+ // Essential for some internal Stencil checks in edge runtimes
167
+ if (typeof globalThis.window === "undefined") {
168
+ globalThis.window = globalThis;
169
+ }
170
+
171
+ const { renderToString } = await import("@authhero/widget/hydrate");
172
+ ```
173
+
174
+ ### Avoiding Hydration Mismatches
175
+
176
+ To prevent hydration errors (where server and client HTML differ):
177
+
178
+ 1. **Use the same data**: Ensure the `screen` and `branding` props match between server and client
179
+ 2. **Avoid client-only conditionals**: Don't conditionally render based on `window` or browser APIs
180
+ 3. **Serialize consistently**: Use the same JSON serialization on server and client
181
+ 4. **Handle edge cases**: Escape special characters in JSON attributes (`'` → `&#39;`)
182
+
183
+ ### Performance Benefits
184
+
185
+ - **Instant Display**: Users see the login form immediately without waiting for JavaScript
186
+ - **Reduced Layout Shift**: Content dimensions are known from the server response
187
+ - **Better SEO**: Search engines can index the rendered content
188
+ - **Improved Core Web Vitals**: Lower LCP (Largest Contentful Paint) and CLS (Cumulative Layout Shift)
189
+
60
190
  ## UI Schema (Auth0 Forms API)
61
191
 
62
192
  The widget renders UI based on Auth0's Forms API schema for universal login flows.
@@ -1 +1 @@
1
- import{p as a,b as e}from"./p-ARKBiJrR.js";export{s as setNonce}from"./p-ARKBiJrR.js";import{g as t}from"./p-DQuL1Twl.js";(()=>{const e=import.meta.url,t={};return""!==e&&(t.resourcesUrl=new URL(".",e).href),a(t)})().then((async a=>(await t(),e([["p-072b155c",[[513,"authhero-node",{component:[16],value:[1],disabled:[4],passwordVisible:[32]}]]],["p-3ae71c86",[[513,"authhero-widget",{screen:[1],apiUrl:[1,"api-url"],baseUrl:[1,"base-url"],state:[1025],screenId:[1025,"screen-id"],authParams:[1,"auth-params"],statePersistence:[1,"state-persistence"],storageKey:[1,"storage-key"],branding:[1],theme:[1],loading:[1028],autoSubmit:[4,"auto-submit"],autoNavigate:[4,"auto-navigate"],_screen:[32],_authParams:[32],_branding:[32],_theme:[32],formData:[32]},null,{screen:[{watchScreen:0}],branding:[{watchBranding:0}],theme:[{watchTheme:0}],authParams:[{watchAuthParams:0}]}]]]],a))));
1
+ import{p as a,b as e}from"./p-ARKBiJrR.js";export{s as setNonce}from"./p-ARKBiJrR.js";import{g as t}from"./p-DQuL1Twl.js";(()=>{const e=import.meta.url,t={};return""!==e&&(t.resourcesUrl=new URL(".",e).href),a(t)})().then((async a=>(await t(),e([["p-30808298",[[513,"authhero-node",{component:[16],value:[1],disabled:[4],passwordVisible:[32]}]]],["p-3ae71c86",[[513,"authhero-widget",{screen:[1],apiUrl:[1,"api-url"],baseUrl:[1,"base-url"],state:[1025],screenId:[1025,"screen-id"],authParams:[1,"auth-params"],statePersistence:[1,"state-persistence"],storageKey:[1,"storage-key"],branding:[1],theme:[1],loading:[1028],autoSubmit:[4,"auto-submit"],autoNavigate:[4,"auto-navigate"],_screen:[32],_authParams:[32],_branding:[32],_theme:[32],formData:[32]},null,{screen:[{watchScreen:0}],branding:[{watchBranding:0}],theme:[{watchTheme:0}],authParams:[{watchAuthParams:0}]}]]]],a))));