From f74474d60605959990fa1e40a47e90565a2583e8 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Tue, 16 Jun 2026 12:31:02 -0700 Subject: [PATCH 1/2] Fix Expo user attribute removal docs --- .../setting-user-properties.mdx | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/content/shared/user-management/setting-user-properties.mdx b/content/shared/user-management/setting-user-properties.mdx index 46f6608..9878f4b 100644 --- a/content/shared/user-management/setting-user-properties.mdx +++ b/content/shared/user-management/setting-user-properties.mdx @@ -5,11 +5,31 @@ description: "Customize paywalls and target users by setting user attributes" By setting user attributes, you can display information about the user on the paywall. You can also define [audiences](/dashboard/dashboard-campaigns/campaigns-audience) in a campaign to determine which paywall to show to a user, based on their user attributes. +:::ios If a paywall uses the **Set user attributes** action, the merged attributes are sent back to your app via `SuperwallDelegate.userAttributesDidChange(newAttributes:)`. +::: -You do this by passing a `[String: Any?]` dictionary of attributes to `Superwall.shared.setUserAttributes(_:)`: +:::android + +If a paywall uses the **Set user attributes** action, the merged attributes are sent back to your app via `SuperwallDelegate.userAttributesDidChange(newAttributes:)`. + +::: + +:::flutter + +If a paywall uses the **Set user attributes** action, the merged attributes are sent back to your app via `SuperwallDelegate.userAttributesDidChange(newAttributes:)`. + +::: + +:::expo + +If a paywall uses the **Set user attributes** action, the merged attributes are sent back to your app via the `onUserAttributesChange` callback in `useSuperwallEvents`. + +::: + +You do this by passing an attributes dictionary or object to the SDK: :::ios @@ -106,9 +126,40 @@ function UserProfile() { ## Usage -This is a merge operation, such that if the existing user attributes dictionary +This is a merge operation, such that if the existing user attributes already has a value for a given property, the old value is overwritten. Other -existing properties will not be affected. To unset/delete a value, you can pass `nil` -for the value. +existing properties will not be affected. + +:::ios +To unset/delete a user attribute, pass `nil` for that attribute name: + +```swift Swift +Superwall.shared.setUserAttributes(["profilePic": nil]) +``` +::: + +:::android +To unset/delete a user attribute, pass `null` for that attribute name: + +```kotlin Kotlin +Superwall.instance.setUserAttributes(mapOf("profilePic" to null)) +``` +::: + +:::flutter +The Flutter SDK accepts non-null user attribute values. It does not currently support deleting an attribute by passing `null`. +::: + +:::expo +To unset/delete a user attribute, pass `null` for that attribute name. `undefined` keys are ignored before they cross the Expo bridge. + +```tsx React Native +await update({ + profilePic: null, +}); +``` + +Nullable user attributes require `expo-superwall` 1.0.5 or later. +::: You can reference user attributes in [audience filters](/dashboard/dashboard-campaigns/campaigns-audience) to help decide when to display your paywall. When you configure your paywall, you can also reference the user attributes in its text variables. For more information on how to that, see [Configuring a Paywall](/dashboard/dashboard-creating-paywalls/paywall-editor-overview). From 1f1ee1d40381c3e56df4fcd95ea513dde0dd2080 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 17 Jun 2026 11:46:39 -0700 Subject: [PATCH 2/2] fix: lazy load docs source routes --- src/routes/api/search.ts | 16 +++++++++------- src/routes/llms[.]mdx.docs.$.ts | 10 +++++----- src/routes/llms[.]mdx.docs.index.ts | 4 +++- src/routes/sdk/{$}[.]md.ts | 10 +++++----- src/routes/sdk/{$}[.]mdx.ts | 10 +++++----- src/routes/sdk[.]md.ts | 4 +++- src/routes/sdk[.]mdx.ts | 4 +++- src/routes/sitemap[.]xml.ts | 2 +- src/routes/{$}[.]md.ts | 10 +++++----- src/routes/{$}[.]mdx.ts | 10 +++++----- vite.config.ts | 8 ++++++++ 11 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/routes/api/search.ts b/src/routes/api/search.ts index caf64a1..93dee21 100644 --- a/src/routes/api/search.ts +++ b/src/routes/api/search.ts @@ -1,15 +1,17 @@ import { createFileRoute } from "@tanstack/react-router"; -import { createDocsSearchApi } from "@/lib/search"; -let server: - | ReturnType - | null = null; +type DocsSearchServer = { + GET(request: Request): Response | Promise; +}; -function getServer() { +let server: DocsSearchServer | null = null; + +async function getServer() { if (server) return server; // In development, the fetch-based search client still hits this route. // The production client loads a prebuilt FlexSearch index instead. + const { createDocsSearchApi } = await import("@/lib/search"); server = createDocsSearchApi(); return server; @@ -23,6 +25,6 @@ export const Route = createFileRoute("/api/search")({ }, }); -export function handleSearchGet(request: Request) { - return getServer().GET(request); +export async function handleSearchGet(request: Request) { + return (await getServer()).GET(request); } diff --git a/src/routes/llms[.]mdx.docs.$.ts b/src/routes/llms[.]mdx.docs.$.ts index aadbf51..248257e 100644 --- a/src/routes/llms[.]mdx.docs.$.ts +++ b/src/routes/llms[.]mdx.docs.$.ts @@ -1,14 +1,14 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; -import { - buildMarkdownRouteResponse, - getMarkdownRouteBody, - slugsFromMarkdownSplat, -} from "@/lib/markdown-route"; export const Route = createFileRoute("/llms.mdx/docs/$")({ server: { handlers: { GET: async ({ params, request }) => { + const { + buildMarkdownRouteResponse, + getMarkdownRouteBody, + slugsFromMarkdownSplat, + } = await import("@/lib/markdown-route"); const slugs = slugsFromMarkdownSplat(params._splat); const body = await getMarkdownRouteBody(slugs, request); if (!body) throw notFound(); diff --git a/src/routes/llms[.]mdx.docs.index.ts b/src/routes/llms[.]mdx.docs.index.ts index fe09780..51916de 100644 --- a/src/routes/llms[.]mdx.docs.index.ts +++ b/src/routes/llms[.]mdx.docs.index.ts @@ -1,10 +1,12 @@ import { createFileRoute } from "@tanstack/react-router"; -import { buildMarkdownRouteResponse, getMarkdownRouteBody } from "@/lib/markdown-route"; export const Route = createFileRoute("/llms.mdx/docs/")({ server: { handlers: { GET: async ({ request }) => { + const { buildMarkdownRouteResponse, getMarkdownRouteBody } = await import( + "@/lib/markdown-route" + ); return buildMarkdownRouteResponse(await getMarkdownRouteBody([], request), []); }, }, diff --git a/src/routes/sdk/{$}[.]md.ts b/src/routes/sdk/{$}[.]md.ts index feb7e76..2055a75 100644 --- a/src/routes/sdk/{$}[.]md.ts +++ b/src/routes/sdk/{$}[.]md.ts @@ -1,15 +1,15 @@ import { createFileRoute } from "@tanstack/react-router"; -import { - buildMarkdownRouteResponse, - getMarkdownForPageSlugs, - slugsFromMarkdownSplat, -} from "@/lib/markdown-route"; async function getSdkMarkdownResponse( params: { _splat?: string }, request: Request, includeBody = true, ) { + const { + buildMarkdownRouteResponse, + getMarkdownForPageSlugs, + slugsFromMarkdownSplat, + } = await import("@/lib/markdown-route"); const slugs = ["sdk", ...slugsFromMarkdownSplat(params._splat)]; const body = await getMarkdownForPageSlugs(slugs, request); return buildMarkdownRouteResponse(includeBody ? body! : null, slugs); diff --git a/src/routes/sdk/{$}[.]mdx.ts b/src/routes/sdk/{$}[.]mdx.ts index cc1d630..7e70e44 100644 --- a/src/routes/sdk/{$}[.]mdx.ts +++ b/src/routes/sdk/{$}[.]mdx.ts @@ -1,15 +1,15 @@ import { createFileRoute } from "@tanstack/react-router"; -import { - buildMarkdownRouteResponse, - getMarkdownForPageSlugs, - slugsFromMarkdownSplat, -} from "@/lib/markdown-route"; async function getSdkMarkdownResponse( params: { _splat?: string }, request: Request, includeBody = true, ) { + const { + buildMarkdownRouteResponse, + getMarkdownForPageSlugs, + slugsFromMarkdownSplat, + } = await import("@/lib/markdown-route"); const slugs = ["sdk", ...slugsFromMarkdownSplat(params._splat)]; const body = await getMarkdownForPageSlugs(slugs, request); return buildMarkdownRouteResponse(includeBody ? body! : null, slugs); diff --git a/src/routes/sdk[.]md.ts b/src/routes/sdk[.]md.ts index 6e6cd75..31be7f5 100644 --- a/src/routes/sdk[.]md.ts +++ b/src/routes/sdk[.]md.ts @@ -1,7 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; -import { buildMarkdownRouteResponse, getMarkdownForPageSlugs } from "@/lib/markdown-route"; async function getSdkMarkdownResponse(request: Request, includeBody = true) { + const { buildMarkdownRouteResponse, getMarkdownForPageSlugs } = await import( + "@/lib/markdown-route" + ); const slugs = ["sdk"]; const body = await getMarkdownForPageSlugs(slugs, request); return buildMarkdownRouteResponse(includeBody ? body! : null, slugs); diff --git a/src/routes/sdk[.]mdx.ts b/src/routes/sdk[.]mdx.ts index 3b1b7b2..714ee6d 100644 --- a/src/routes/sdk[.]mdx.ts +++ b/src/routes/sdk[.]mdx.ts @@ -1,7 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; -import { buildMarkdownRouteResponse, getMarkdownForPageSlugs } from "@/lib/markdown-route"; async function getSdkMarkdownResponse(request: Request, includeBody = true) { + const { buildMarkdownRouteResponse, getMarkdownForPageSlugs } = await import( + "@/lib/markdown-route" + ); const slugs = ["sdk"]; const body = await getMarkdownForPageSlugs(slugs, request); return buildMarkdownRouteResponse(includeBody ? body! : null, slugs); diff --git a/src/routes/sitemap[.]xml.ts b/src/routes/sitemap[.]xml.ts index f975404..7e34d44 100644 --- a/src/routes/sitemap[.]xml.ts +++ b/src/routes/sitemap[.]xml.ts @@ -1,11 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; -import { source } from "@/lib/source"; import { buildSitemapXml, getSitemapEntries } from "@/lib/sitemap"; export const Route = createFileRoute("/sitemap.xml")({ server: { handlers: { GET: async () => { + const { source } = await import("@/lib/source"); const pagePaths = source.getPages().map((page) => page.url); const xml = buildSitemapXml(getSitemapEntries(pagePaths)); diff --git a/src/routes/{$}[.]md.ts b/src/routes/{$}[.]md.ts index 149b00a..99513f0 100644 --- a/src/routes/{$}[.]md.ts +++ b/src/routes/{$}[.]md.ts @@ -1,15 +1,15 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; -import { - buildMarkdownRouteResponse, - getMarkdownForPageSlugs, - slugsFromMarkdownSplat, -} from "@/lib/markdown-route"; async function getMarkdownResponse( params: { _splat?: string }, request: Request, includeBody = true, ) { + const { + buildMarkdownRouteResponse, + getMarkdownForPageSlugs, + slugsFromMarkdownSplat, + } = await import("@/lib/markdown-route"); const slugs = slugsFromMarkdownSplat(params._splat); const body = await getMarkdownForPageSlugs(slugs, request); if (!body) throw notFound(); diff --git a/src/routes/{$}[.]mdx.ts b/src/routes/{$}[.]mdx.ts index d1dde82..fa88fa9 100644 --- a/src/routes/{$}[.]mdx.ts +++ b/src/routes/{$}[.]mdx.ts @@ -1,15 +1,15 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; -import { - buildMarkdownRouteResponse, - getMarkdownForPageSlugs, - slugsFromMarkdownSplat, -} from "@/lib/markdown-route"; async function getMarkdownResponse( params: { _splat?: string }, request: Request, includeBody = true, ) { + const { + buildMarkdownRouteResponse, + getMarkdownForPageSlugs, + slugsFromMarkdownSplat, + } = await import("@/lib/markdown-route"); const slugs = slugsFromMarkdownSplat(params._splat); const body = await getMarkdownForPageSlugs(slugs, request); if (!body) throw notFound(); diff --git a/vite.config.ts b/vite.config.ts index 1e7b9b4..97dd165 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -66,6 +66,14 @@ export default defineConfig({ tanstackStart({ router: { basepath: "/docs", + codeSplittingOptions: { + defaultBehavior: [ + ["loader"], + ["component"], + ["pendingComponent"], + ["errorComponent", "notFoundComponent"], + ], + }, }, prerender: { enabled: true,