Vite error when importing from .server file. "Error: Server-only module referenced by client" #9035
Replies: 11 comments 32 replies
-
#8448 maybe you'll find something useful here |
Beta Was this translation helpful? Give feedback.
-
I am encountering a similar problem. Yet don't know if it's related.
Even though the specified import is only used within the action function. Everything works great when using optimizeDeps: {
entries: [
'**/routes/**/*.{ts,tsx}',
],
}, Anyone got an idea about this? |
Beta Was this translation helpful? Give feedback.
-
For what it's worth: I had a similar problem:
but it turned out, I had imported a function from the I assume this is because remix determines if an import from a .server.js file is actually used in the loader function when deciding to throw this error or not. |
Beta Was this translation helpful? Give feedback.
-
OK now I'm having trouble integrating Trigger.dev because their initialization, which is based on file name routing rather than folder name routing, triggers this error. The automatically generated import { createRemixRoute } from "@trigger.dev/remix";
import { client } from "~/trigger.server";
// Remix will automatically strip files with side effects
// So you need to *export* your Job definitions like this:
export * from "~/jobs/example.server";
export const { action } = createRemixRoute(client); And when I try to build I get the error:
Now, I don't think this should be an error and I think it's on the Remix side, but I'm not sure if anyone is working on this issue or if it's just not going to be fixed but I'd like to be able to integrate Remix with Trigger.dev so I'll try to see if they have any tips as well. |
Beta Was this translation helpful? Give feedback.
-
it also happens when you re-export route relevant modules imported from other file.
`+loader.tsx`
import { db, post } from '~/server/driver/db.server';
export const loader: LoaderFunction = async () => {
const result = await db.select().from(post).all();
return result;
};
...clientLoader, meta, etc `route.tsx`
...
export { loader, clientLoader, meta, shouldRevalidate } from './+loader'; I splitted +loader.file into two, one that exports export { clientLoader, meta, shouldRevalidate } from './+meta';
export { action, headers, loader } from './+server';
export default Route; |
Beta Was this translation helpful? Give feedback.
-
This issue arises, when you have *.server files / folder used outside route modules, because remix does not remove *.server imports. It removes just imports from route modules which are only used within loader and action. If it happens to be a *.server import from the route module file, then everything is fine. But when the *.server import is hidden further down the import-chain, remix isn't going to remove it and so the server-only check will fail. Additionally, pure |
Beta Was this translation helpful? Give feedback.
-
okay so if we're not supposed to use actions outside of routes, how do I do something like... have a signout button in a header that's present everywhere? It seems heavy-handed that Remix should dictate that my header must be part of my dashboard component and can't be a separate file... |
Beta Was this translation helpful? Give feedback.
-
We faced this issue during the migration from Remix v2 to RR7. We were using remix-flat-routes that are not configured correctly if you only follow the migration guide.
Vite could pick up the routes correctly and strip away the server modules during the build and local "dev" process. Hope this helps! |
Beta Was this translation helpful? Give feedback.
-
🐛 Error: "Server-only module referenced by client" (Vite + Remix)✅ Solution found — scroll down for step-by-step fix We ran into the following Vite error when trying to use a
💡 ContextFile structure:
In import { callAnAPI } from "../utils/example.server"; // ❌ Causes the error This breaks because client-side code can’t directly import server-only modules. ✅ Solution: Use an API route to bridge client ↔ serverWe resolved it by moving the server logic into a server-side action route and calling it from the hook using 🔧 Step-by-Step Fix1. Create a server-side action route
import type { ActionFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
export const action: ActionFunction = async ({ request, context }) => {
const { callAnAPI } = await import("../utils/example.server");
const { session } = context;
const shop = session?.shop;
const formData = await request.formData();
const chunk = JSON.parse(formData.get("chunk") as string);
try {
const response = await callAnAPI(shop, chunk);
if (response.ok) {
return json({ success: true, failedProducts: [] });
}
return json({ success: false, failedProducts: chunk });
} catch (error: any) {
console.error("Server error:", error);
return json({ success: false, failedProducts: chunk, error: error.message }, { status: 500 });
}
}; 2. Call the server route from the hook
import { useFetcher } from "@remix-run/react";
export function useSendProducts() {
const fetcher = useFetcher();
const sendProducts = async (chunk: Product[]): Promise<{ success: boolean; failedProducts: Product[] }> => {
try {
const formData = new FormData();
formData.append("chunk", JSON.stringify(chunk));
fetcher.submit(formData, {
method: "POST",
action: "/api/send-products",
});
return new Promise((resolve) => {
const checkFetcher = setInterval(() => {
if (fetcher.state === "idle" && fetcher.data) {
clearInterval(checkFetcher);
const { success, failedProducts, error } = fetcher.data;
if (error) console.error("Fetcher error:", error);
resolve({ success, failedProducts: failedProducts || chunk });
}
}, 100);
});
} catch (error) {
console.error("Error sending products:", error);
return { success: false, failedProducts: chunk };
}
};
return { sendProducts };
} 3. Use server-only logic in the loader (safe)
import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
export async function loader({ request }: LoaderFunctionArgs) {
const { getProducts } = await import("../utils/example.server");
const { session } = await authenticate.admin(request);
const shop = session.shop;
const result = await getProducts(shop);
return json({ result });
} In your component: export default function ExamplePage() {
const { result } = useLoaderData<typeof loader>();
return <pre>{JSON.stringify(result, null, 2)}</pre>;
} ✅ Outcome
|
Beta Was this translation helpful? Give feedback.
-
Hi @kiliman, I’ve shared a detailed answer in the discussion #9035 that I believe could help others facing the same issue. Would it be possible to pin or mark my comment as the answer to make it more visible to the community? I’d appreciate your consideration! Thanks, Mahdi Hamldar |
Beta Was this translation helpful? Give feedback.
-
All of these solutions seem backwards. The entire point of Remix is to write full-stack applications. Loaders, actions, and the rendered component/page are all in the same file. Loaders and actions should be able to import .server files Observations:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello.
I have a file named validation.server.jsx that I use to export a schema validation function.
As the name indicates, it is a .server file with code to run exclusively server-side.
I then use the exported function from the referred file to validate data inside the action of my route.jsx.
Now with Vite, Im getting the error below just by importing the function from the validation.server.jsx file into my app/routes/admin/route.jsx.
import { validateSchema } from "../../lib/validation.server"
By making this import I get the error or any import from a .server file into a route.jsx file i get the error:
It seems that it was dealt with before but im still getting this problem: #8267
Beta Was this translation helpful? Give feedback.
All reactions