132 lines
4.1 KiB
Plaintext
132 lines
4.1 KiB
Plaintext
---
|
|
title: Excluding server-only code from admin UI
|
|
label: Excluding server code
|
|
order: 70
|
|
desc: Learn how to exclude server-only code from the Payload Admin UI bundle
|
|
---
|
|
|
|
Because the Admin Panel browser bundle includes your Payload Config file, files using server-only modules need to be excluded.
|
|
It's common for your config to rely on server only modules to perform logic in access control functions, hooks, and other contexts.
|
|
|
|
Any file that imports a server-only module such as `fs`, `stripe`, `authorizenet`, `nodemailer`, etc. **cannot** be included in the browser bundle.
|
|
|
|
#### Example Scenario
|
|
|
|
Say we have a collection called `Subscriptions` that has a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload.
|
|
|
|
**Collection config**:
|
|
|
|
```ts
|
|
// collections/Subscriptions/index.ts
|
|
|
|
import { CollectionConfig } from 'payload/types'
|
|
import createStripeSubscription from './hooks/createStripeSubscription'
|
|
|
|
export const Subscription: CollectionConfig = {
|
|
slug: 'subscriptions',
|
|
hooks: {
|
|
beforeChange: [createStripeSubscription],
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'stripeSubscriptionID',
|
|
type: 'text',
|
|
required: true,
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
**Collection hook**:
|
|
|
|
```ts
|
|
// collections/Subscriptions/hooks/createStripeSubscription.ts
|
|
|
|
// highlight-start
|
|
import Stripe from 'stripe' // <-- server-only module
|
|
// highlight-end
|
|
|
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
|
|
|
|
export const createStripeSubscription = async ({ data, operation }) => {
|
|
if (operation === 'create') {
|
|
const dataWithStripeID = { ...data }
|
|
|
|
// use Stripe to create a Stripe subscription
|
|
const subscription = await stripe.subscriptions.create({
|
|
// Configure the subscription accordingly
|
|
})
|
|
|
|
// Automatically add the Stripe subscription ID
|
|
// to the data that will be saved to this Subscription doc
|
|
dataWithStripeID.stripeSubscriptionID = subscription.id
|
|
|
|
return dataWithStripeID
|
|
}
|
|
|
|
return data
|
|
}
|
|
```
|
|
|
|
<Banner type="error">
|
|
<strong>Warning:</strong>
|
|
<br />
|
|
The above code is NOT production-ready and should not be referenced to create Stripe
|
|
subscriptions. Although creating a beforeChange hook is a completely valid spot to do things like
|
|
create subscriptions, the code above is incomplete and insecure, meant for explanation purposes
|
|
only.
|
|
</Banner>
|
|
|
|
**As-is, this collection will prevent your Admin panel from bundling or loading correctly, because Stripe relies on some Node-only packages.**
|
|
|
|
#### How to fix this
|
|
|
|
You need to make sure that you use `alias`es to tell your bundler to import "safe" files vs. attempting to import any server-side code that you need to get rid of. Depending on your bundler (Webpack, Vite, etc.) the steps involved may be slightly different.
|
|
|
|
The basic idea is to create a file that exports an empty object, and then alias import paths of any files that import server-only modules to that empty object file.
|
|
|
|
This way when your bundler goes to import a file that contains server-only modules, it will instead import the empty object file, which will not break the browser bundle.
|
|
|
|
### Aliasing server-only modules
|
|
|
|
To remove files that contain server-only modules from your bundle, you can use an `alias`.
|
|
|
|
First create new file that exports an empty object:
|
|
```js
|
|
// mocks/emptyObject.js
|
|
|
|
export default {}
|
|
```
|
|
|
|
Then, in your Payload config, you can alias the file containing the server-only module to the mock module. For example, here's how you'd do this in Webpack:
|
|
|
|
```ts
|
|
// payload.config.ts
|
|
|
|
import { buildConfig } from 'payload/config'
|
|
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
|
|
|
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js')
|
|
const pathToFileWithServerOnlyModule = path.resolve(__dirname, 'hooks/syncStripeCustomer.ts')
|
|
|
|
export default buildConfig({
|
|
admin: {
|
|
bundler: webpackBundler(),
|
|
webpack: (config) => {
|
|
return {
|
|
...config,
|
|
resolve: {
|
|
...config.resolve,
|
|
// highlight-start
|
|
alias: {
|
|
...config.resolve.alias,
|
|
[pathToFileWithServerOnlyModule]: mockModulePath,
|
|
},
|
|
// highlight-end
|
|
},
|
|
}
|
|
},
|
|
},
|
|
})
|
|
```
|