Skip to content

feat(ui): lazy-load phone country code data#7848

Open
jacekradko wants to merge 1 commit intomainfrom
jacekradko/lazy-load-phone-data
Open

feat(ui): lazy-load phone country code data#7848
jacekradko wants to merge 1 commit intomainfrom
jacekradko/lazy-load-phone-data

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Feb 14, 2026

Description

Split the 12KB countryCodeData.ts module into its own async chunk, loaded only when PhoneInput is rendered. This removes static data (~245 countries) from the ui-common chunk, reducing its size and improving initial load performance for applications that don't use phone functionality.

Changes

  • Created countryCodeDataLoader.ts with a dynamic import and sync cache pattern
  • Updated phoneUtils.ts, useFormattedPhoneNumber.ts, and PhoneInput to use lazy getters with US fallback
  • Added rspack cache group configuration to isolate the phone country data chunk
  • Updated 8 test files to preload data in beforeAll

Verification

  • pnpm build passes; phone-country-data chunk created (~8.7KB raw, 3.8KB gzip, within 10KB budget)
  • pnpm test passes (1548 tests across 109 files)
  • Country data completely removed from ui-common

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

Release Notes

  • Chores
    • Optimized bundle size by implementing lazy loading for country code data used in phone input features.
    • Updated test infrastructure to ensure country code data is properly initialized before running tests.
    • Improved resource management through separate bundling and caching of phone-country-data.

Move the 582-line countryCodeData module (~8.7KB) out of the ui-common
chunk and into its own async chunk (phone-country-data) loaded on demand
when PhoneInput renders.

- Create countryCodeDataLoader.ts with dynamic import and sync cache
- Update phoneUtils.ts to use lazy getters with US fallback
- Update useFormattedPhoneNumber.ts to use lazy getters
- Update PhoneInput to load data before rendering via useCountryCodeData hook
- Exclude countryCodeData from ui-common cache group in rspack config
- Add bundlewatch entry for new phone-country-data chunk (10KB budget)
- Add loadCountryCodeData() to test files that need country data
@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

⚠️ No Changeset found

Latest commit: 8dcfa14

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Feb 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Feb 14, 2026 4:47am

Request Review

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 14, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7848

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7848

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7848

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7848

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7848

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7848

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7848

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7848

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7848

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7848

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@7848

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7848

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7848

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7848

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7848

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7848

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7848

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7848

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7848

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7848

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7848

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7848

commit: 8dcfa14

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

This change implements lazy-loading of country code data by introducing a new loader module that asynchronously imports and caches country code maps. The bundler configuration is updated to create a separate chunk for the country code data. Components and utilities are refactored to use getter functions instead of direct static imports. Test files are updated to preload the country code data before execution to ensure data availability during testing.

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (200 files):

⚔️ .changeset/changelog.js (content)
⚔️ .changeset/config.json (content)
⚔️ .gitignore (content)
⚔️ commitlint.config.ts (content)
⚔️ eslint.config.mjs (content)
⚔️ integration/presets/longRunningApps.ts (content)
⚔️ integration/presets/next.ts (content)
⚔️ integration/templates/index.ts (content)
⚔️ integration/templates/next-app-router/src/middleware.ts (content)
⚔️ integration/templates/react-vite/src/buttons/index.tsx (content)
⚔️ integration/templates/react-vite/src/main.tsx (content)
⚔️ integration/tests/next-quickstart-keyless.test.ts (content)
⚔️ integration/tests/oauth-flows.test.ts (content)
⚔️ integration/tests/tanstack-start/keyless.test.ts (content)
⚔️ package.json (content)
⚔️ packages/astro/package.json (content)
⚔️ packages/astro/src/astro-components/unstyled/CheckoutButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/SignInButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/SignOutButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/SignUpButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro (content)
⚔️ packages/astro/src/astro-components/unstyled/utils.ts (content)
⚔️ packages/astro/src/env.d.ts (content)
⚔️ packages/astro/src/integration/create-integration.ts (content)
⚔️ packages/astro/src/internal/create-clerk-instance.ts (content)
⚔️ packages/astro/src/internal/merge-env-vars-with-params.ts (content)
⚔️ packages/astro/src/server/clerk-middleware.ts (content)
⚔️ packages/astro/src/server/get-safe-env.ts (content)
⚔️ packages/astro/src/types.ts (content)
⚔️ packages/backend/package.json (content)
⚔️ packages/backend/src/api/endpoints/UserApi.ts (content)
⚔️ packages/backend/tsup.config.ts (content)
⚔️ packages/clerk-js/bundlewatch.config.json (content)
⚔️ packages/clerk-js/src/core/__tests__/clerk.test.ts (content)
⚔️ packages/clerk-js/src/core/auth/safeLock.ts (content)
⚔️ packages/clerk-js/src/core/clerk.ts (content)
⚔️ packages/clerk-js/src/core/resources/Client.ts (content)
⚔️ packages/clerk-js/src/core/resources/PublicUserData.ts (content)
⚔️ packages/clerk-js/src/core/resources/SignIn.ts (content)
⚔️ packages/clerk-js/src/core/resources/SignUp.ts (content)
⚔️ packages/clerk-js/src/core/resources/__tests__/Client.test.ts (content)
⚔️ packages/clerk-js/src/core/resources/__tests__/PublicUserData.test.ts (content)
⚔️ packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts (content)
⚔️ packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts (content)
⚔️ packages/express/src/__tests__/clerkMiddleware.test.ts (content)
⚔️ packages/express/src/__tests__/helpers.ts (content)
⚔️ packages/express/src/authenticateRequest.ts (content)
⚔️ packages/express/src/types.ts (content)
⚔️ packages/express/src/utils.ts (content)
⚔️ packages/localizations/src/nl-BE.ts (content)
⚔️ packages/localizations/src/nl-NL.ts (content)
⚔️ packages/nextjs/src/app-router/client/ClerkProvider.tsx (content)
⚔️ packages/nextjs/src/app-router/server/ClerkProvider.tsx (content)
⚔️ packages/nextjs/src/app-router/server/keyless-provider.tsx (content)
⚔️ packages/nextjs/src/pages/ClerkProvider.tsx (content)
⚔️ packages/nextjs/src/server/__tests__/__snapshots__/exports.test.ts.snap (content)
⚔️ packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts (content)
⚔️ packages/nextjs/src/server/clerkMiddleware.ts (content)
⚔️ packages/nextjs/src/server/constants.ts (content)
⚔️ packages/nextjs/src/server/index.ts (content)
⚔️ packages/nextjs/src/server/protect.ts (content)
⚔️ packages/nextjs/src/server/types.ts (content)
⚔️ packages/nextjs/src/types.ts (content)
⚔️ packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts (content)
⚔️ packages/nuxt/src/module.ts (content)
⚔️ packages/react-router/src/client/ReactRouterClerkProvider.tsx (content)
⚔️ packages/react-router/src/client/types.ts (content)
⚔️ packages/react-router/src/client/usePathnameWithoutSplatRouteParams.tsx (content)
⚔️ packages/react-router/src/server/clerkMiddleware.ts (content)
⚔️ packages/react-router/src/server/loadOptions.ts (content)
⚔️ packages/react-router/src/server/types.ts (content)
⚔️ packages/react-router/src/server/utils.ts (content)
⚔️ packages/react-router/src/utils/env.ts (content)
⚔️ packages/react/src/stateProxy.ts (content)
⚔️ packages/shared/src/constants.ts (content)
⚔️ packages/shared/src/internal/clerk-js/constants.ts (content)
⚔️ packages/shared/src/keyless/index.ts (content)
⚔️ packages/shared/src/keyless/service.ts (content)
⚔️ packages/shared/src/proxy.ts (content)
⚔️ packages/shared/src/types/json.ts (content)
⚔️ packages/shared/src/types/session.ts (content)
⚔️ packages/shared/src/types/signInFuture.ts (content)
⚔️ packages/tanstack-react-start/src/client/ClerkProvider.tsx (content)
⚔️ packages/tanstack-react-start/src/client/types.ts (content)
⚔️ packages/tanstack-react-start/src/client/uiComponents.tsx (content)
⚔️ packages/tanstack-react-start/src/client/utils.ts (content)
⚔️ packages/tanstack-react-start/src/server/constants.ts (content)
⚔️ packages/tanstack-react-start/src/server/keyless/utils.ts (content)
⚔️ packages/tanstack-react-start/src/server/utils/index.ts (content)
⚔️ packages/tanstack-react-start/src/utils/env.ts (content)
⚔️ packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts (content)
⚔️ packages/ui/bundlewatch.config.json (content)
⚔️ packages/ui/rspack.config.js (content)
⚔️ packages/ui/src/ClerkUI.ts (content)
⚔️ packages/ui/src/__tests__/__snapshots__/exports.test.ts.snap (content)
⚔️ packages/ui/src/__tests__/exports.test.ts (content)
⚔️ packages/ui/src/common/InfiniteListSpinner.tsx (content)
⚔️ packages/ui/src/common/NotificationCountBadge.tsx (content)
⚔️ packages/ui/src/common/PrintableComponent.tsx (content)
⚔️ packages/ui/src/common/organizations/OrganizationPreview.tsx (content)
⚔️ packages/ui/src/components/APIKeys/APIKeyModal.tsx (content)
⚔️ packages/ui/src/components/APIKeys/ApiKeysTable.tsx (content)
⚔️ packages/ui/src/components/APIKeys/CopyAPIKeyModal.tsx (content)
⚔️ packages/ui/src/components/APIKeys/RevokeAPIKeyConfirmationModal.tsx (content)
⚔️ packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx (content)
⚔️ packages/ui/src/components/ImpersonationFab/index.tsx (content)
⚔️ packages/ui/src/components/OAuthConsent/OAuthConsent.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/ActiveMembersList.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/DomainList.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/InvitedMembersList.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/MembersActions.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/OrganizationGeneralPage.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/OrganizationMembersTabInvitations.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/OrganizationMembersTabRequests.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx (content)
⚔️ packages/ui/src/components/OrganizationProfile/RequestToJoinList.tsx (content)
⚔️ packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx (content)
⚔️ packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx (content)
⚔️ packages/ui/src/components/PricingTable/PricingTableDefault.tsx (content)
⚔️ packages/ui/src/components/SignIn/SignInFactorTwo.tsx (content)
⚔️ packages/ui/src/components/SignIn/SignInStart.tsx (content)
⚔️ packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx (content)
⚔️ packages/ui/src/components/SignIn/__tests__/utils.test.ts (content)
⚔️ packages/ui/src/components/SignIn/lazy-sign-up.ts (content)
⚔️ packages/ui/src/components/SignUp/SignUpStart.tsx (content)
⚔️ packages/ui/src/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx (content)
⚔️ packages/ui/src/components/Statements/Statement.tsx (content)
⚔️ packages/ui/src/components/Subscriptions/SubscriptionsList.tsx (content)
⚔️ packages/ui/src/components/UserButton/SessionActions.tsx (content)
⚔️ packages/ui/src/components/UserButton/UserButtonTopLevelIdentifier.tsx (content)
⚔️ packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx (content)
⚔️ packages/ui/src/components/UserProfile/DeleteSection.tsx (content)
⚔️ packages/ui/src/components/UserProfile/MfaBackupCodeList.tsx (content)
⚔️ packages/ui/src/components/UserProfile/MfaPhoneCodeScreen.tsx (content)
⚔️ packages/ui/src/components/UserProfile/PasswordSection.tsx (content)
⚔️ packages/ui/src/components/UserProfile/UsernameSection.tsx (content)
⚔️ packages/ui/src/components/UserProfile/__tests__/MfaPage.test.tsx (content)
⚔️ packages/ui/src/components/UserProfile/__tests__/PhoneSection.test.tsx (content)
⚔️ packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx (content)
⚔️ packages/ui/src/customizables/elementDescriptors.ts (content)
⚔️ packages/ui/src/elements/Actions.tsx (content)
⚔️ packages/ui/src/elements/Alert.tsx (content)
⚔️ packages/ui/src/elements/ArrowBlockButton.tsx (content)
⚔️ packages/ui/src/elements/Avatar.tsx (content)
⚔️ packages/ui/src/elements/Badge.tsx (content)
⚔️ packages/ui/src/elements/Card/CardAlert.tsx (content)
⚔️ packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx (content)
⚔️ packages/ui/src/elements/Card/CardContent.tsx (content)
⚔️ packages/ui/src/elements/ClipboardInput.tsx (content)
⚔️ packages/ui/src/elements/Drawer.tsx (content)
⚔️ packages/ui/src/elements/FieldControl.tsx (content)
⚔️ packages/ui/src/elements/Header.tsx (content)
⚔️ packages/ui/src/elements/InputGroup.tsx (content)
⚔️ packages/ui/src/elements/InputWithIcon.tsx (content)
⚔️ packages/ui/src/elements/LegalConsentCheckbox.tsx (content)
⚔️ packages/ui/src/elements/Modal.tsx (content)
⚔️ packages/ui/src/elements/Navbar.tsx (content)
⚔️ packages/ui/src/elements/OrganizationPreview.tsx (content)
⚔️ packages/ui/src/elements/PasswordInput.tsx (content)
⚔️ packages/ui/src/elements/PhoneInput/__tests__/useFormattedPhoneNumber.test.ts (content)
⚔️ packages/ui/src/elements/PhoneInput/index.tsx (content)
⚔️ packages/ui/src/elements/PhoneInput/useFormattedPhoneNumber.ts (content)
⚔️ packages/ui/src/elements/PreviewButton.tsx (content)
⚔️ packages/ui/src/elements/ProfileCard/ProfileCardContent.tsx (content)
⚔️ packages/ui/src/elements/ProfileCard/ProfileCardRoot.tsx (content)
⚔️ packages/ui/src/elements/Select.tsx (content)
⚔️ packages/ui/src/elements/SuccessPage.tsx (content)
⚔️ packages/ui/src/elements/Switch.tsx (content)
⚔️ packages/ui/src/elements/TagInput.tsx (content)
⚔️ packages/ui/src/elements/UserPreview.tsx (content)
⚔️ packages/ui/src/entry.ts (content)
⚔️ packages/ui/src/hooks/index.ts (content)
⚔️ packages/ui/src/index.ts (content)
⚔️ packages/ui/src/internal/appearance.ts (content)
⚔️ packages/ui/src/primitives/AlertIcon.tsx (content)
⚔️ packages/ui/src/primitives/Badge.tsx (content)
⚔️ packages/ui/src/primitives/Button.tsx (content)
⚔️ packages/ui/src/primitives/FormErrorText.tsx (content)
⚔️ packages/ui/src/primitives/FormSuccessText.tsx (content)
⚔️ packages/ui/src/primitives/Spinner.tsx (content)
⚔️ packages/ui/src/primitives/Table.tsx (content)
⚔️ packages/ui/src/primitives/Th.tsx (content)
⚔️ packages/ui/src/primitives/gapPropertyCompat.ts (content)
⚔️ packages/ui/src/router/BaseRouter.tsx (content)
⚔️ packages/ui/src/router/PathRouter.tsx (content)
⚔️ packages/ui/src/server.ts (content)
⚔️ packages/ui/src/test/create-fixtures.tsx (content)
⚔️ packages/ui/src/test/fixture-helpers.ts (content)
⚔️ packages/ui/src/utils/__tests__/formatSafeIdentifier.test.ts (content)
⚔️ packages/ui/src/utils/__tests__/phoneUtils.test.ts (content)
⚔️ packages/ui/src/utils/phoneUtils.ts (content)
⚔️ packages/ui/tsdown.config.mts (content)
⚔️ packages/upgrade/src/versions/core-3/changes/clerk-types-deprecation.md (content)
⚔️ packages/upgrade/src/versions/core-3/index.js (content)
⚔️ pnpm-lock.yaml (content)
⚔️ scripts/canary-core3.mjs (content)
⚔️ scripts/canary.mjs (content)
⚔️ scripts/common.mjs (content)
⚔️ scripts/snapshot.mjs (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(ui): lazy-load phone country code data' accurately and specifically describes the main change: implementing lazy loading of phone country code data.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/utils/phoneUtils.ts (1)

27-46: ⚠️ Potential issue | 🟠 Major

Async country data load can return incorrect results on first call.
These functions kick off loadCountryCodeData() but immediately read from the maps. On a cold call, the maps are still empty and the logic falls back to US, which mis-parses non‑US numbers. This is a functional regression unless every caller preloads the data.

Please either make these APIs async and await the load, or fail fast when data isn’t loaded to force callers to preload (instead of silently returning the US fallback).

Also applies to: 103-146

🧹 Nitpick comments (1)
packages/ui/src/elements/PhoneInput/countryCodeDataLoader.ts (1)

46-48: Missing explicit return type annotation.

Per coding guidelines, functions should have explicit return types. The getSubAreaCodeSets function lacks a return type annotation unlike the other getter functions in this module.

Suggested fix
-export function getSubAreaCodeSets() {
+export function getSubAreaCodeSets(): { us: ReadonlySet<string>; ca: ReadonlySet<string> } | undefined {
   return subAreaCodeSets;
 }

As per coding guidelines: "Always define explicit return types for functions, especially public APIs".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant