Reduce bundle size: extract getSession and improve tree-shaking #1334

Open
opened 2026-03-13 08:33:34 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @paol-imi on GitHub (Jun 9, 2025).

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

We use better-auth in a serverless context. The library adds ~500 kB to our bundle, which directly impacts cold start time. Ideally, the library would allow better tree-shaking and more granular control on which API get used or not.

Describe the solution you'd like

An initial smaller step could be to extract getSession into a standalone utility. This would allow patterns like lazy-loading the library inside signin/signout routes only when needed, while keeping the rest of the app lightweight by using getSession directly.

Describe alternatives you've considered

  • Manually copying just the needed parts of getSession
  • Replacing the library in cold-path endpoints

Additional context

No response

Originally created by @paol-imi on GitHub (Jun 9, 2025). Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. We use better-auth in a serverless context. The library adds ~500 kB to our bundle, which directly impacts cold start time. Ideally, the library would allow better tree-shaking and more granular control on which API get used or not. ### Describe the solution you'd like An initial smaller step could be to extract `getSession` into a standalone utility. This would allow patterns like lazy-loading the library inside signin/signout routes only when needed, while keeping the rest of the app lightweight by using getSession directly. ### Describe alternatives you've considered - Manually copying just the needed parts of getSession - Replacing the library in cold-path endpoints ### Additional context _No response_
Author
Owner

@mlafeldt commented on GitHub (Oct 21, 2025):

I'm currently evaluating Better Auth as a Clerk replacement, and I also noticed that it's not made for serverless environments.

Using it in my Astro project (deployed to Cloudflare Pages) adds a whopping 1400K to the bundle.

Before (using Clerk):

...
   222.7K  dist/_worker.js/chunks/astro/server_BeLhyc_3.mjs
   241.1K  dist/_worker.js/chunks/index_LICAQjiJ.mjs
   510.9K  dist/_worker.js/chunks/_@astro-renderers_DgFsZQUB.mjs
  2075.3K  total (57 modules)

After (using Better Auth):

...
   255.3K  dist/_worker.js/_astro-internal_middleware.mjs
   545.3K  dist/_worker.js/chunks/_@astro-renderers_CNzeWT4e.mjs
  1396.8K  dist/_worker.js/chunks/auth_4uEdegud.mjs
  3503.7K  total (55 modules)

One big optimization I was able to implement is to stub out Kysley. Turns out Better Auth will still pull it in even when using Drizzle. With the Kysley hack/stub:

...
   255.3K  dist/_worker.js/_astro-internal_middleware.mjs
   545.3K  dist/_worker.js/chunks/_@astro-renderers_CNzeWT4e.mjs
   889.9K  dist/_worker.js/chunks/auth_O5bSokMB.mjs
  2996.8K  total (55 modules)

Here's the Vite plugin that does the stubbing:

// Stub out kysely to reduce bundle size (better-auth imports it even with drizzle adapter)
function stubKysely(): Plugin {
  return {
    name: 'stub-kysely',
    enforce: 'pre',
    resolveId(id) {
      if (id === 'kysely') {
        return '\0kysely-stub'
      }
    },
    load(id) {
      if (id === '\0kysely-stub') {
        return `
          export function getKyselyDatabaseType() { return null; }
          export class Kysely {}
          export class SqliteDialect {}
          export class MysqlDialect {}
          export class PostgresDialect {}
          export class MssqlDialect {}
          export class CompiledQuery {}
          export class DefaultQueryCompiler {}
          export const sql = {}
          export const DEFAULT_MIGRATION_TABLE = ''
          export const DEFAULT_MIGRATION_LOCK_TABLE = ''
        `
      }
    },
  }
}

1 MB still feels like a lot, esp. considering that all the rest of the serverless project is 2 MB.

I don't know if there's any effective tree shaking happening right now, but basically everything (including social providers I don't use) gets deployed as part of the bundle.

@mlafeldt commented on GitHub (Oct 21, 2025): I'm currently evaluating Better Auth as a Clerk replacement, and I also noticed that it's not made for serverless environments. Using it in my Astro project (deployed to Cloudflare Pages) adds a whopping **1400K** to the bundle. Before (using Clerk): ``` ... 222.7K dist/_worker.js/chunks/astro/server_BeLhyc_3.mjs 241.1K dist/_worker.js/chunks/index_LICAQjiJ.mjs 510.9K dist/_worker.js/chunks/_@astro-renderers_DgFsZQUB.mjs 2075.3K total (57 modules) ``` After (using Better Auth): ``` ... 255.3K dist/_worker.js/_astro-internal_middleware.mjs 545.3K dist/_worker.js/chunks/_@astro-renderers_CNzeWT4e.mjs 1396.8K dist/_worker.js/chunks/auth_4uEdegud.mjs 3503.7K total (55 modules) ``` One big optimization I was able to implement is to stub out Kysley. Turns out Better Auth will still pull it in even when using Drizzle. With the Kysley hack/stub: ``` ... 255.3K dist/_worker.js/_astro-internal_middleware.mjs 545.3K dist/_worker.js/chunks/_@astro-renderers_CNzeWT4e.mjs 889.9K dist/_worker.js/chunks/auth_O5bSokMB.mjs 2996.8K total (55 modules) ``` Here's the Vite plugin that does the stubbing: <details> ```ts // Stub out kysely to reduce bundle size (better-auth imports it even with drizzle adapter) function stubKysely(): Plugin { return { name: 'stub-kysely', enforce: 'pre', resolveId(id) { if (id === 'kysely') { return '\0kysely-stub' } }, load(id) { if (id === '\0kysely-stub') { return ` export function getKyselyDatabaseType() { return null; } export class Kysely {} export class SqliteDialect {} export class MysqlDialect {} export class PostgresDialect {} export class MssqlDialect {} export class CompiledQuery {} export class DefaultQueryCompiler {} export const sql = {} export const DEFAULT_MIGRATION_TABLE = '' export const DEFAULT_MIGRATION_LOCK_TABLE = '' ` } }, } } ``` </details> 1 MB still feels like a lot, esp. considering that all the rest of the serverless project is 2 MB. I don't know if there's any effective tree shaking happening right now, but basically everything (including social providers I don't use) gets deployed as part of the bundle.
Author
Owner

@paol-imi commented on GitHub (Oct 21, 2025):

To the best of my knowledge, the library is currently quite difficult to tree-shake largely because the codebase has tightly coupled components – everything from the database adapter (Kysely) to HTTP API endpoints is bundled together. This makes sense as a result of for the "batteries-included" approach the team has been taking, prioritizing feature completeness and out-of-the-box functionality, which was clearly key to the library's rapid adoption and early success.

It's clear there's room for improvement in terms of internal codebase design. A full refactor to make everything properly modular would require substantial design work and time, and I imagine it's hard to prioritize that in the near future given the project's growth trajectory. However, an initial roadmap that could have low impact on the current codebase would be to carefully externalize specific utilities and rely on lazy loading patterns.

For example, if getSession were extracted as a standalone utility, we could do something like:

// router.ts
export const router = createRouter({
  auth: lazy(() => import("./router/auth")),
  example: lazy(() => import("./router/example"))
});
// routes/example.ts
import { getSession } from "better-auth";

export const handler = (req: Request) => {
  const session = getSession(req);
  if (!session) throw new Error("401 Unauthorized");
  // From here we can access the session...
};

This way, the heavy auth logic only loads in routes that actually need it (signin/signout), while most of the app stays lightweight. The getSession import would be tree-shakeable with almost no footprint on bundle size.

@mlafeldt's findings about Kysely being pulled in even when using Drizzle is a perfect example of how the tight coupling is affecting bundle size in practice. Would love to see even incremental improvements here.

@paol-imi commented on GitHub (Oct 21, 2025): To the best of my knowledge, the library is currently quite difficult to tree-shake largely because the codebase has tightly coupled components – everything from the database adapter (Kysely) to HTTP API endpoints is bundled together. This makes sense as a result of for the "batteries-included" approach the team has been taking, prioritizing feature completeness and out-of-the-box functionality, which was clearly key to the library's rapid adoption and early success. It's clear there's room for improvement in terms of internal codebase design. A full refactor to make everything properly modular would require substantial design work and time, and I imagine it's hard to prioritize that in the near future given the project's growth trajectory. However, an **initial roadmap** that could have **low impact** on the current codebase would be to carefully externalize specific utilities and rely on lazy loading patterns. For example, if getSession were extracted as a standalone utility, we could do something like: ```ts // router.ts export const router = createRouter({ auth: lazy(() => import("./router/auth")), example: lazy(() => import("./router/example")) }); ``` ```ts // routes/example.ts import { getSession } from "better-auth"; export const handler = (req: Request) => { const session = getSession(req); if (!session) throw new Error("401 Unauthorized"); // From here we can access the session... }; ``` This way, the heavy auth logic only loads in routes that actually need it (signin/signout), while most of the app stays lightweight. The getSession import would be tree-shakeable with almost no footprint on bundle size. @mlafeldt's findings about Kysely being pulled in even when using Drizzle is a perfect example of how the tight coupling is affecting bundle size in practice. Would love to see even incremental improvements here.
Author
Owner

@paol-imi commented on GitHub (Oct 21, 2025):

@Kinfe123 should we move this to a discussion?

@paol-imi commented on GitHub (Oct 21, 2025): @Kinfe123 should we move this to a discussion?
Author
Owner

@Bekacru commented on GitHub (Oct 21, 2025):

This should be addressed on 1.4 release. We're working on having a minimal import for those who aren't using kysley directly. And we're refactoring out passkey and other plugins to make them a separate package.

In the meantime I suggest importing plugins from their designated path instead of better-auth/plugins (like for two factor from better-auth/plugins/two-factor) to avoid importing all plugins we export

@Bekacru commented on GitHub (Oct 21, 2025): This should be addressed on 1.4 release. We're working on having a minimal import for those who aren't using kysley directly. And we're refactoring out `passkey` and other plugins to make them a separate package. In the meantime I suggest importing plugins from their designated path instead of `better-auth/plugins` (like for two factor from `better-auth/plugins/two-factor`) to avoid importing all plugins we export
Author
Owner

@mlafeldt commented on GitHub (Oct 21, 2025):

This should be addressed on 1.4 release. We're working on having a minimal import for those who aren't using kysley directly. And we're refactoring out passkey and other plugins to make them a separate package.

That's great news! Looking forward to 1.4 then.

In the meantime I suggest importing plugins from their designated path instead of better-auth/plugins (like for two factor from better-auth/plugins/two-factor) to avoid importing all plugins we export

I don't use any plugins right now, so I probably can't benefit from this. I measured the bundle sizes with emailAndPassword only.

@mlafeldt commented on GitHub (Oct 21, 2025): > This should be addressed on 1.4 release. We're working on having a minimal import for those who aren't using kysley directly. And we're refactoring out `passkey` and other plugins to make them a separate package. That's great news! Looking forward to 1.4 then. > In the meantime I suggest importing plugins from their designated path instead of `better-auth/plugins` (like for two factor from `better-auth/plugins/two-factor`) to avoid importing all plugins we export I don't use any plugins right now, so I probably can't benefit from this. I measured the bundle sizes with `emailAndPassword` only.
Author
Owner

@himself65 commented on GitHub (Oct 21, 2025):

I will handle this

@himself65 commented on GitHub (Oct 21, 2025): I will handle this
Author
Owner

@sabaturgay commented on GitHub (Nov 1, 2025):

Thanks for this excellent library!

I wanted to provide additional context regarding bundle size. While reviewing the esbuild meta file, I noticed that zod/locales alone adds 173kb to the bundle. It would be great if this could either be removed entirely or made optional for those who need it.

@sabaturgay commented on GitHub (Nov 1, 2025): Thanks for this excellent library! I wanted to provide additional context regarding bundle size. While reviewing the esbuild meta file, I noticed that `zod/locales` alone adds **173kb** to the bundle. It would be great if this could either be removed entirely or made optional for those who need it.
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 1, 2025):

Based on your feedback, a good next step would be to implement a twofold approach:

  1. Decouple getSession so that it can be imported separately (or lazy-loaded) from the rest of the library. This would let you include only what you need in cold-path endpoints and avoid loading unused functionality [1].

  2. Investigate making the zod locales dependency optional or even removable. Since zod/locales is adding a significant chunk (~173 kB) to your bundle, you might either configure your bundler to ignore it (for instance, via a plugin or alias) or propose a change to better-auth so that these locales are loaded only when needed [1].

I recommend opening a pull request with these improvements or testing a fork with these changes to verify the impact on your cold start times. This approach should help you reduce the bundle size noticeably while maintaining functionality where needed [1].

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 1, 2025): Based on your feedback, a good next step would be to implement a twofold approach: 1. Decouple getSession so that it can be imported separately (or lazy-loaded) from the rest of the library. This would let you include only what you need in cold-path endpoints and avoid loading unused functionality [[1]](https://github.com/better-auth/better-auth/issues/2964). 2. Investigate making the zod locales dependency optional or even removable. Since zod/locales is adding a significant chunk (~173 kB) to your bundle, you might either configure your bundler to ignore it (for instance, via a plugin or alias) or propose a change to better-auth so that these locales are loaded only when needed [[1]](https://github.com/better-auth/better-auth/issues/2964). I recommend opening a pull request with these improvements or testing a fork with these changes to verify the impact on your cold start times. This approach should help you reduce the bundle size noticeably while maintaining functionality where needed [[1]](https://github.com/better-auth/better-auth/issues/2964). <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@mlafeldt commented on GitHub (Nov 1, 2025):

Is this AI slop supposed to add anything to the conversation? 😐

@mlafeldt commented on GitHub (Nov 1, 2025): Is this AI slop supposed to add anything to the conversation? 😐
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 1, 2025):

Based on the request, the next step is to extract getSession into its own module so that it can be lazy-loaded in sign‑in/sign‑out routes rather than bundled into the main application code. This approach can allow your bundler to remove unused API parts and reduce cold start overhead.

To troubleshoot, first verify that your bundler (for example, Webpack or Rollup) is configured for optimal tree‑shaking (such as setting sideEffects: false if applicable). Then refactor getSession as a standalone utility and test dynamic imports to ensure that only the required code is loaded when needed [1].

If tree‑shaking still struggles, consider checking if any other side effects in the library prevent proper elimination of unused code. This iterative process should help you isolate and minimize the bundle size for better performance in a serverless context.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 1, 2025): Based on the request, the next step is to extract getSession into its own module so that it can be lazy-loaded in sign‑in/sign‑out routes rather than bundled into the main application code. This approach can allow your bundler to remove unused API parts and reduce cold start overhead. To troubleshoot, first verify that your bundler (for example, Webpack or Rollup) is configured for optimal tree‑shaking (such as setting sideEffects: false if applicable). Then refactor getSession as a standalone utility and test dynamic imports to ensure that only the required code is loaded when needed [[1]](https://github.com/better-auth/better-auth/issues/2964). If tree‑shaking still struggles, consider checking if any other side effects in the library prevent proper elimination of unused code. This iterative process should help you isolate and minimize the bundle size for better performance in a serverless context. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Jan 31, 2026):

Hi, @paol-imi. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You raised concerns about better-auth's large bundle size (~500 kB), affecting serverless cold start times.
  • You suggested extracting getSession as a standalone utility to improve tree-shaking and enable lazy loading.
  • Other users confirmed even larger bundle sizes (up to 1.4 MB) and shared workarounds like stubbing out Kysely.
  • The maintainer mentioned upcoming improvements in version 1.4, including minimal imports and plugin refactoring.
  • The discussion also touched on modularization and making dependencies like zod/locales optional to optimize bundle size.

Next Steps:

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting below.
  • If I don’t hear back within 7 days, I will automatically close this issue to keep the backlog manageable.

Thanks for your understanding and contribution!

@dosubot[bot] commented on GitHub (Jan 31, 2026): Hi, @paol-imi. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You raised concerns about better-auth's large bundle size (~500 kB), affecting serverless cold start times. - You suggested extracting `getSession` as a standalone utility to improve tree-shaking and enable lazy loading. - Other users confirmed even larger bundle sizes (up to 1.4 MB) and shared workarounds like stubbing out Kysely. - The maintainer mentioned upcoming improvements in version 1.4, including minimal imports and plugin refactoring. - The discussion also touched on modularization and making dependencies like zod/locales optional to optimize bundle size. **Next Steps:** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting below. - If I don’t hear back within 7 days, I will automatically close this issue to keep the backlog manageable. Thanks for your understanding and contribution!
Author
Owner

@mlafeldt commented on GitHub (Feb 7, 2026):

I will handle this

Closing as not planned is the opposite of handling this.

@mlafeldt commented on GitHub (Feb 7, 2026): > I will handle this Closing as not planned is the opposite of handling this.
Author
Owner

@himself65 commented on GitHub (Feb 7, 2026):

I will handle this

Closing as not planned is the opposite of handling this.

This would take a lot of work. I'm going to move the code to the correct files

@himself65 commented on GitHub (Feb 7, 2026): > > I will handle this > > Closing as not planned is the opposite of handling this. This would take a lot of work. I'm going to move the code to the correct files
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1334