Skip to content
27 changes: 21 additions & 6 deletions dev-packages/e2e-tests/test-applications/solidstart/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { sentrySolidStartVite } from '@sentry/solidstart';
import { withSentry } from '@sentry/solidstart';
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
vite: {
plugins: [sentrySolidStartVite()],
},
});
export default defineConfig(
withSentry(
{},
{
// Typically we want to default to ./src/instrument.sever.ts
// `withSentry` would then build and copy the file over to
// the .output folder, but since we can't use the production
// server for our e2e tests, we have to delete the build folders
// prior to using the dev server for our tests. Which also gets
// rid of the instrument.server.mjs file that we need to --import.
// Therefore, we specify the .mjs file here and to ensure
// `withSentry` gets its file to build and we continue to reference
// the file from the `src` folder for --import without needing to
// transpile before.
// This can be removed once we get the production server to work
// with our e2e tests.
instrumentation: './src/instrument.server.mjs',
},
),
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ test('captures an exception', async ({ page }) => {
});

await page.goto('/error-boundary');
// The first page load causes a hydration error on the dev server sometimes - a reload works around this
await page.reload();
await page.locator('#caughtErrorBtn').click();
const errorEvent = await errorEventPromise;

Expand Down
2 changes: 1 addition & 1 deletion packages/solidstart/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = {
},
},
{
files: ['src/vite/**', 'src/server/**'],
files: ['src/vite/**', 'src/server/**', 'src/config/**'],
rules: {
'@sentry-internal/sdk/no-optional-chaining': 'off',
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
Expand Down
120 changes: 83 additions & 37 deletions packages/solidstart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mount(() => <StartClient />, document.getElementById('app'));

### 3. Server-side Setup

Create an instrument file named `instrument.server.mjs` and add your initialization code for the server-side SDK.
Create an instrument file named `src/instrument.server.ts` and add your initialization code for the server-side SDK.

```javascript
import * as Sentry from '@sentry/solidstart';
Expand Down Expand Up @@ -101,16 +101,94 @@ export default defineConfig({
The Sentry middleware enhances the data collected by Sentry on the server side by enabling distributed tracing between
the client and server.

### 5. Run your application
### 5. Configure your application

For Sentry to work properly, SolidStart's `app.config.ts` has to be modified. Wrap your config with `withSentry` and
configure it to upload source maps.

If your `instrument.server.ts` file is not located in the `src` folder, you can specify the path via the
`instrumentation` option to `withSentry`.

To upload source maps, configure an auth token. Auth tokens can be passed explicitly with the `authToken` option, with a
`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when
building your project. We recommend adding the auth token to your CI/CD environment as an environment variable.

Learn more about configuring the plugin in our
[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin).

```typescript
import { defineConfig } from '@solidjs/start/config';
import { withSentry } from '@sentry/solidstart';

export default defineConfig(
withSentry(
{
// SolidStart config
middleware: './src/middleware.ts',
},
{
// Sentry `withSentry` options
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
debug: true,
// optional: if your `instrument.server.ts` file is not located inside `src`
instrumentation: './mypath/instrument.server.ts',
},
),
);
```

### 6. Run your application

Then run your app

```bash
NODE_OPTIONS='--import=./instrument.server.mjs' yarn start
# or
NODE_OPTIONS='--require=./instrument.server.js' yarn start
NODE_OPTIONS='--import=./.output/server/instrument.server.mjs' yarn start
```

⚠️ **Note build presets** ⚠️
Depending on [build preset](https://nitro.unjs.io/deploy), the location of `instrument.server.mjs` differs. To find out
where `instrument.server.mjs` is located, monitor the build log output for

```bash
[Sentry SolidStart withSentry] Successfully created /my/project/path/.output/server/instrument.server.mjs.
```

⚠️ **Note for platforms without the ability to modify `NODE_OPTIONS` or use `--import`** ⚠️
Depending on where the application is deployed to, it might not be possible to modify or use `NODE_OPTIONS` to import
`instrument.server.mjs`.

For such platforms, we offer the `experimental_basicServerTracing` flag to add a top level import of
`instrument.server.mjs` to the server entry file.

```typescript
import { defineConfig } from '@solidjs/start/config';
import { withSentry } from '@sentry/solidstart';

export default defineConfig(
withSentry(
{
// ...
middleware: './src/middleware.ts',
},
{
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
debug: true,
// optional: if your `instrument.server.ts` file is not located inside `src`
instrumentation: './mypath/instrument.server.ts',
// optional: if NODE_OPTIONS or --import is not avaiable
experimental_basicServerTracing: true,
},
),
);
```

This has a **fundamental restriction**: It only supports limited performance instrumentation. **Only basic http
instrumentation** will work, and no DB or framework-specific instrumentation will be available.

# Solid Router

The Solid Router instrumentation uses the Solid Router library to create navigation spans to ensure you collect
Expand Down Expand Up @@ -156,35 +234,3 @@ render(
document.getElementById('root'),
);
```

## Uploading Source Maps

To upload source maps, add the `sentrySolidStartVite` plugin from `@sentry/solidstart` to your `app.config.ts` and
configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a
`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when
building your project. We recommend you add the auth token to your CI/CD environment as an environment variable.

Learn more about configuring the plugin in our
[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin).

```typescript
// app.config.ts
import { defineConfig } from '@solidjs/start/config';
import { sentrySolidStartVite } from '@sentry/solidstart';

export default defineConfig({
// ...

vite: {
plugins: [
sentrySolidStartVite({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
debug: true,
}),
],
},
// ...
});
```
95 changes: 95 additions & 0 deletions packages/solidstart/src/config/addInstrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as fs from 'fs';
import * as path from 'path';
import { consoleSandbox } from '@sentry/utils';
import type { Nitro } from './types';

// Nitro presets for hosts that only host static files
export const staticHostPresets = ['github_pages'];
// Nitro presets for hosts that use `server.mjs` as opposed to `index.mjs`
export const serverFilePresets = ['netlify'];

/**
* Adds the built `instrument.server.js` file to the output directory.
*
* This will no-op if no `instrument.server.js` file was found in the
* build directory. Make sure the `sentrySolidStartVite` plugin was
* added to `app.config.ts` to enable building the instrumentation file.
*/
export async function addInstrumentationFileToBuild(nitro: Nitro): Promise<void> {
// Static file hosts have no server component so there's nothing to do
if (staticHostPresets.includes(nitro.options.preset)) {
return;
}

const buildDir = nitro.options.buildDir;
const serverDir = nitro.options.output.serverDir;
const source = path.resolve(buildDir, 'build', 'ssr', 'instrument.server.js');
const destination = path.resolve(serverDir, 'instrument.server.mjs');

try {
await fs.promises.copyFile(source, destination);

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry SolidStart withSentry] Successfully created ${destination}.`);
});
} catch (error) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(`[Sentry SolidStart withSentry] Failed to create ${destination}.`, error);
});
}
}

/**
* Adds an `instrument.server.mjs` import to the top of the server entry file.
*
* This is meant as an escape hatch and should only be used in environments where
* it's not possible to `--import` the file instead as it comes with a limited
* tracing experience, only collecting http traces.
*/
export async function experimental_addInstrumentationFileTopLevelImportToServerEntry(
serverDir: string,
preset: string,
): Promise<void> {
// Static file hosts have no server component so there's nothing to do
if (staticHostPresets.includes(preset)) {
return;
}

const instrumentationFile = path.resolve(serverDir, 'instrument.server.mjs');
const serverEntryFileName = serverFilePresets.includes(preset) ? 'server.mjs' : 'index.mjs';
const serverEntryFile = path.resolve(serverDir, serverEntryFileName);

try {
await fs.promises.access(instrumentationFile, fs.constants.F_OK);
} catch (error) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry SolidStart withSentry] Failed to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`,
error,
);
});
return;
}

try {
const content = await fs.promises.readFile(serverEntryFile, 'utf-8');
const updatedContent = `import './instrument.server.mjs';\n${content}`;
await fs.promises.writeFile(serverEntryFile, updatedContent);

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[Sentry SolidStart withSentry] Added \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`,
);
});
} catch (error) {
// eslint-disable-next-line no-console
console.warn(
`[Sentry SolidStart withSentry] An error occurred when trying to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`,
error,
);
}
}
2 changes: 2 additions & 0 deletions packages/solidstart/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './withSentry';
export type { Nitro, SentrySolidStartConfigOptions } from './types';
35 changes: 35 additions & 0 deletions packages/solidstart/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { defineConfig } from '@solidjs/start/config';
// Types to avoid pulling in extra dependencies
// These are non-exhaustive
export type Nitro = {
options: {
buildDir: string;
output: {
serverDir: string;
};
preset: string;
};
};

export type SolidStartInlineConfig = Parameters<typeof defineConfig>[0];

export type SolidStartInlineServerConfig = {
hooks?: {
close?: () => unknown;
'rollup:before'?: (nitro: Nitro) => unknown;
};
};

export type SentrySolidStartConfigOptions = {
/**
* Enabling basic server tracing can be used for environments where modifying the node option `--import` is not possible.
* However, enabling this option only supports limited tracing instrumentation. Only http traces will be collected (but no database-specific traces etc.).
*
* If this option is `true`, the Sentry SDK will import the instrumentation.server.ts|js file at the top of the server entry file to load the SDK on the server.
*
* **DO NOT** enable this option if you've already added the node option `--import` in your node start script. This would initialize Sentry twice on the server-side and leads to unexpected issues.
*
* @default false
*/
experimental_basicServerTracing?: boolean;
};
63 changes: 63 additions & 0 deletions packages/solidstart/src/config/withSentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { addSentryPluginToVite } from '../vite';
import type { SentrySolidStartPluginOptions } from '../vite/types';
import {
addInstrumentationFileToBuild,
experimental_addInstrumentationFileTopLevelImportToServerEntry,
} from './addInstrumentation';
import type { Nitro, SolidStartInlineConfig, SolidStartInlineServerConfig } from './types';

/**
* Modifies the passed in Solid Start configuration with build-time enhancements such as
* building the `instrument.server.ts` file into the appropriate build folder based on
* build preset.
*
* @param solidStartConfig A Solid Start configuration object, as usually passed to `defineConfig` in `app.config.ts|js`
* @param sentrySolidStartPluginOptions Options to configure the plugin
* @returns The modified config to be exported and passed back into `defineConfig`
*/
export const withSentry = (
solidStartConfig: SolidStartInlineConfig = {},
sentrySolidStartPluginOptions: SentrySolidStartPluginOptions = {},
): SolidStartInlineConfig => {
const server = (solidStartConfig.server || {}) as SolidStartInlineServerConfig;
const hooks = server.hooks || {};
const vite =
typeof solidStartConfig.vite === 'function'
? (...args: unknown[]) => addSentryPluginToVite(solidStartConfig.vite(...args), sentrySolidStartPluginOptions)
: addSentryPluginToVite(solidStartConfig.vite, sentrySolidStartPluginOptions);

let serverDir: string;
let buildPreset: string;

return {
...solidStartConfig,
vite,
server: {
...server,
hooks: {
...hooks,
async close() {
if (sentrySolidStartPluginOptions.experimental_basicServerTracing) {
await experimental_addInstrumentationFileTopLevelImportToServerEntry(serverDir, buildPreset);
}

// Run user provided hook
if (hooks.close) {
hooks.close();
}
},
async 'rollup:before'(nitro: Nitro) {
serverDir = nitro.options.output.serverDir;
buildPreset = nitro.options.preset;

await addInstrumentationFileToBuild(nitro);

// Run user provided hook
if (hooks['rollup:before']) {
hooks['rollup:before'](nitro);
}
},
},
},
};
};
1 change: 1 addition & 0 deletions packages/solidstart/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './server';
export * from './vite';
export * from './config';
Loading
Loading