S
SolidJS•6d ago
MaveriX89

renderToString in Elysia BFF Layer

Random question, I have a Solid SPA with a BFF server (implemented using Elysia) -- so this is not Solid Start. This all lives under a single repository with the following root, high-level structure:
- shared (contains things shared across both Elysia BFF and Solid SPA)
- server (Elysia BFF: contains only normal TypeScript files)
- src (SolidJS SPA: contains TSX files)
- tsconfig.json (TS config for entire repo)
- vite.config.ts (Vite config for SPA)
- shared (contains things shared across both Elysia BFF and Solid SPA)
- server (Elysia BFF: contains only normal TypeScript files)
- src (SolidJS SPA: contains TSX files)
- tsconfig.json (TS config for entire repo)
- vite.config.ts (Vite config for SPA)
I am in the process of integrating an email verification feature in my Elysia server. I am sending the email using Resend like so:
// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: `<p>Click <a href="${url}">here</a> to verify your email.</p>`
});
// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: `<p>Click <a href="${url}">here</a> to verify your email.</p>`
});
The question I have is related to that html field. I would like to use a SolidJS component to layout the HTML structure of the email content leveraging the renderToString Solid API. This would add a single .tsx file in that layer. That'll convert things to the following:
// VerifyEmail.tsx (note the TSX extension)
import { renderToString } from "solid-js/web"

export const Email = (props: { url: string }) => {
return (
<html>
<body>
<h2>Onboarding</h2>
<p>
Click <a href={props.url}>here</a> to verify your email.
</p>
</body>
</html>
)
}

export const getEmailTemplate = (url: string) => renderToString(() => <Email url={url} />)

// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: getEmailTemplate(url);
});
// VerifyEmail.tsx (note the TSX extension)
import { renderToString } from "solid-js/web"

export const Email = (props: { url: string }) => {
return (
<html>
<body>
<h2>Onboarding</h2>
<p>
Click <a href={props.url}>here</a> to verify your email.
</p>
</body>
</html>
)
}

export const getEmailTemplate = (url: string) => renderToString(() => <Email url={url} />)

// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: getEmailTemplate(url);
});
I put my component in a separate module within my Elysia BFF layer and imported the getEmailTemplate in my module that sends the email. But when I run the code, I get the following error:
ReferenceError: React is not defined
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/Email.tsx:16:71)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:559:34)
at createRoot (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/dist/server.js:58:14)
at renderToString (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:557:14)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:53:15)
at sendVerificationEmail (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:48:37)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/better-auth/dist/api/index.mjs:238:52)
ReferenceError: React is not defined
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/Email.tsx:16:71)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:559:34)
at createRoot (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/dist/server.js:58:14)
at renderToString (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:557:14)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:53:15)
at sendVerificationEmail (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:48:37)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/better-auth/dist/api/index.mjs:238:52)
This is my first time doing something like this and not sure what the proper way to do it is. A few thoughts: - Why the heck is React called out when I am using SolidJS? Feels like IDEs assume React too much šŸ˜† - Is this the way I should be doing things? Am I allowed to have the convenience of using a component here? - If it is possible, what do I need to be aware of when it comes to bundling the server now that I have a SolidJS component living there when I didn't have any before? Appreciate any input and insights here.
30 Replies
foolswisdom
foolswisdom•6d ago
I don't have a lot of insight into this problem, but it's likely related to the issue that react JSX is a global namespace (while solid's must be imported). So they don't coexist in the same project very well This is definitely a problem that has come up for people. Try searching for global namespace or other descriptions of the problem in discord search (or github issues) There can also be issues with tooling like esbuild just assuming that all jsx is react (unrelated to namespacing)
Madaxen86
Madaxen86•6d ago
Maybe just add this to tsconfig
"jsx": "preserve",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"jsxImportSource": "solid-js",
Not totally sure, but I think renderToString must be called on the server.
MaveriX89
MaveriX89OP•6d ago
@Madaxen86 yeah, I had all that already set in my typescript config since cI have a Solid SPA. All of the things I’m attempting is happening in my backend-for-frontend layer using Elysia. @foolswisdom yeah, it’s interesting. I’m using Vite to build my Solid SPA and using bun to build/run my server. Bun is configured to read my tsconfig.json and everything is set to point to Solid from a JSX standpoint.
foolswisdom
foolswisdom•6d ago
Before you added this layer, was bun processing the jsx? Wondering if this might be a general bun issue I for some reason thought that you added react to project. Could it be the elysia automatically installs it and is messing things up somehow?
MaveriX89
MaveriX89OP•6d ago
Nah, this whole problem started when I added a Solid component to my Elysia server layer because I’m in the process of adding email verification for Auth. My server is doing the work and I want to express the email’s HTML markup as a Solid component and then stringify the whole thing That’s a good question about whether something within Elysia brings React in somehow.. I even have my bunfig.toml JSX config set to Solid just for good measure and still the same outcome.
foolswisdom
foolswisdom•6d ago
Is there an equivalent of npm list? To see if there's react somewhere
MaveriX89
MaveriX89OP•6d ago
I assume there is. I’d be surprised if Bun doesn’t have that implemented yet. I’m using Bun for everything except for building/bundling the Frontend. I’ll give it a shot when I get back in front of my computer There’s a part of me that also has suspicions around how the file extensions are playing with each other (e.g. my importing of a TSX file within a normal TS file). Everything in my Elysia layer is TS except for that one component module.
Brendonovich
Brendonovich•6d ago
If bun is doing the TS transpiling then it'll need to know how to transform the JSX, TS might just be doing typechecking
MaveriX89
MaveriX89OP•6d ago
Yeah, Bun should be picking up what I have configured in my tsconfig for sure according to their docs. I also added a JSX pragma in the TSX module for good measure and set it to solid-js
Brendonovich
Brendonovich•6d ago
I don't think that's sufficient to actually perform the JSX transform, that requires the babel plugin
MaveriX89
MaveriX89OP•6d ago
Do I really need Babel though? Bun can natively process JS/TS and JSX/TSX out of the box. Unless there’s a dot that I’m missing in the whole mental model
Brendonovich
Brendonovich•6d ago
Since solid uses a custom JSX transform it's necessary to have the babel plugin run on it Bun only supports the React JSX transform
MaveriX89
MaveriX89OP•6d ago
Ah interesting. I thought they had some basic support since in the bunfig.toml I can set both jsx: preserve and jsxImportSource: ā€œsolidā€
Brendonovich
Brendonovich•6d ago
Nah Solid's compiler does all the work of JSX transformation, hence requiring jsx: preserve
MaveriX89
MaveriX89OP•6d ago
So now the question becomes how to add the babel transform into Bun’s toolchain. I haven’t done that myself yet. I’ll be in front of a computer soon to try things out
foolswisdom
foolswisdom•6d ago
I had assumed Babel was already in the mix since there is already a solid SPA... I guess it's being used via the vite plugin, so it's not obvious to users
Brendonovich
Brendonovich•6d ago
I think this is a separate app to the spa
MaveriX89
MaveriX89OP•5d ago
Precisely. I have a full stack app repo consisting of a Solid SPA using the Solid Vite plugin and an Elysia BFF server. Everything spoken about here is happening within the BFF layer which is not using the Vite plugin because I run the Elysia server using Bun (not Vite) Funny enough, I decided to make a post in the Bun discord about this to see what people had to say and someone already posted some not so nice words about Solid https://discord.com/channels/876711213126520882/1365321218898526250 I was suspecting I probably have to make use of a Bun plugin that allows me to use the custom Babel transform that the Solid component I’m using in my Elysia server needs
Your name's not...
Your name's not...•3d ago
I unfortunately don't know how Bun handles these things, but I wish you luck. That comment in the Bun Discord is just weird and sarcastic, I don't understand the motive behind it. I'm not using Bun, but I am doing a BFF with a Go backend, so I feel some of your pain and wish you luck in finding your solution.
mdynnl
mdynnl•3d ago
here you go https://bun.sh/docs/runtime/plugins
bun a @babel/core babel-preset-solid solid-js
bun a -d @types/babel__core # optional
bun a @babel/core babel-preset-solid solid-js
bun a -d @types/babel__core # optional
// bunfig.toml
preload = ["./src/solidPlugin.ts"] #
// bunfig.toml
preload = ["./src/solidPlugin.ts"] #
// ./src/solidPlugin.ts
import { plugin } from "bun";

await plugin({
name: "solid plugin",
async setup(build) {
// @ts-ignore if you've skipped @types/babel__core
const babel = await import("@babel/core");

build.onLoad({ filter: /\.[tj]sx$/ }, async (args) => {
const result = await babel.transformFileAsync(args.path, {
presets: ["@babel/preset-typescript", ["solid", { generate: "ssr" }]],
});


if (result?.code) {
console.log(result.code);
return {
loader: "js",
contents: result.code,
};
}
});
},
});
// ./src/solidPlugin.ts
import { plugin } from "bun";

await plugin({
name: "solid plugin",
async setup(build) {
// @ts-ignore if you've skipped @types/babel__core
const babel = await import("@babel/core");

build.onLoad({ filter: /\.[tj]sx$/ }, async (args) => {
const result = await babel.transformFileAsync(args.path, {
presets: ["@babel/preset-typescript", ["solid", { generate: "ssr" }]],
});


if (result?.code) {
console.log(result.code);
return {
loader: "js",
contents: result.code,
};
}
});
},
});
Madaxen86
Madaxen86•3d ago
GitHub
GitHub - DaniGuardiola/bun-plugin-solid: A plugin to compile Solid....
A plugin to compile Solid.js with Bun. Contribute to DaniGuardiola/bun-plugin-solid development by creating an account on GitHub.
Madaxen86
Madaxen86•3d ago
Haha, same idea I guess. @mdynnl
mdynnl
mdynnl•3d ago
alternatively with that plugin
// ./src/solidPlugin.ts
import { plugin } from "bun";

// @ts-ignore
import { SolidPlugin } from "bun-plugin-solid";
await plugin(SolidPlugin({ generate: "ssr" }));
// ./src/solidPlugin.ts
import { plugin } from "bun";

// @ts-ignore
import { SolidPlugin } from "bun-plugin-solid";
await plugin(SolidPlugin({ generate: "ssr" }));
you need to configure preload in bunfig.toml last time i checked it didn't support plugins during runtime OP already seems to have tsconfig configured correctly so not posting that
MaveriX89
MaveriX89OP•3d ago
Omg, you guys are all incredible! Thank you so much for sharing all of this info!! Just for reference, here's what my tsconfig lookis like:
{
"compilerOptions": {
/* Base Options: */
"baseUrl": ".",
"outDir": "dist",
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"allowJs": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,

/* Strictness */
"strict": true,
"allowUnreachableCode": false,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

/* JSX */
"jsx": "preserve",
"jsxImportSource": "solid-js",

/* Types */
"types": ["vite/client", "bun-types", "@testing-library/jest-dom"],
"paths": {
"@/web/*": ["./src/*"],
"@/shared/*": ["./shared/*"],
"@/server/*": ["./server/*"]
}
},
"exclude": ["node_modules", "coverage", "dist", "public"]
}
{
"compilerOptions": {
/* Base Options: */
"baseUrl": ".",
"outDir": "dist",
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"allowJs": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,

/* Strictness */
"strict": true,
"allowUnreachableCode": false,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

/* JSX */
"jsx": "preserve",
"jsxImportSource": "solid-js",

/* Types */
"types": ["vite/client", "bun-types", "@testing-library/jest-dom"],
"paths": {
"@/web/*": ["./src/*"],
"@/shared/*": ["./shared/*"],
"@/server/*": ["./server/*"]
}
},
"exclude": ["node_modules", "coverage", "dist", "public"]
}
mdynnl
mdynnl•3d ago
important bit for typescript to know about solid
"jsx": "preserve",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"jsxImportSource": "solid-js",
i'm not sure if bun already reads from tsconfig's paths though
MaveriX89
MaveriX89OP•3d ago
Yeah, I believe Bun should automatically infer things from tsconfig according to their docs
mdynnl
mdynnl•3d ago
great if it does
MaveriX89
MaveriX89OP•3d ago
Btw, if using the preload script that you shared above, will that run automatically when running bun or bun build ? So I use bun to run my server in development but then I compile it with bun build for production
mdynnl
mdynnl•3d ago
i think you probably need to use bun bundler api add add that plugin with generate: ssr
MaveriX89
MaveriX89OP•3d ago
You guys are incredible! I was able to get past this blocker! This is all in development mode at least -- now going to try doing a production build to see if it all still works

Did you find this page helpful?