SolidStart entrypoints: app.tsx for isomorphic root, entry-client.tsx for browser initialization, entry-server.tsx for SSR setup, app.config.ts for build configuration.
Complete guide to customizing SolidStart entrypoints. Entrypoints control how your app initializes on both client and server.
The App component is the isomorphic (shared on server and browser) entry point. This is where code runs on both sides.
import { A, Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
export default function App() {
return (
<Router
root={(props) => (
<>
<nav>
<A href="/">Index</A>
<A href="/about">About</A>
</nav>
<Suspense>{props.children}</Suspense>
</>
)}
>
<FileRoutes />
</Router>
);
}
Critical: Always wrap router root with <Suspense> - routes are lazy-loaded.
export default function App() {
return (
<main>
<h1>Hello world!</h1>
</main>
);
}
Key points:
entry-client.tsx is where the application starts in the browser. It mounts the app to the DOM.
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);
import { mount, StartClient } from "@solidjs/start/client";
// Register service workers
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
// Client-only initialization
console.log("Client starting");
mount(() => <StartClient />, document.getElementById("app")!);
Use cases:
Important: This file runs only in the browser, not on the server.
entry-server.tsx is where the application starts on the server. It defines the HTML document structure.
import { createHandler, StartServer } from "@solidjs/start/server";
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
import { createHandler, StartServer } from "@solidjs/start/server";
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="My SolidStart app" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/styles.css" />
{assets}
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
Configure SSR mode in createHandler:
import { createHandler, StartServer } from "@solidjs/start/server";
// Sync SSR (default)
export default createHandler(() => <StartServer document={...} />);
// Async SSR
export default createHandler(
() => <StartServer document={...} />,
{ mode: "async" }
);
// Streaming SSR
export default createHandler(
() => <StartServer document={...} />,
{ mode: "stream" }
);
Document props:
assets: CSS and other asset linkschildren: Rendered app contentscripts: JavaScript bundlesThe app.config.ts is the root configuration file for SolidStart, Vinxi, Vite, and Nitro.
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({});
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({
server: {
preset: "node-server", // or "vercel", "netlify", etc.
},
vite: {
// Vite configuration
build: {
target: "esnext",
},
},
nitro: {
// Nitro configuration
preset: "node-server",
},
});
Configuration options:
server: Server preset and settingsvite: Vite build configurationnitro: Nitro server configurationrouter: Router configurationssr: SSR mode settings// app.tsx
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import { ThemeProvider } from "./contexts/ThemeContext";
export default function App() {
return (
<ThemeProvider>
<Router root={(props) => <Suspense>{props.children}</Suspense>}>
<FileRoutes />
</Router>
</ThemeProvider>
);
}
// app.tsx
import { ErrorBoundary } from "solid-js";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
export default function App() {
return (
<ErrorBoundary fallback={(err) => <div>Error: {err.toString()}</div>}>
<Router root={(props) => <Suspense>{props.children}</Suspense>}>
<FileRoutes />
</Router>
</ErrorBoundary>
);
}
// entry-client.tsx
import { mount, StartClient } from "@solidjs/start/client";
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("SW registered:", registration);
})
.catch((error) => {
console.log("SW registration failed:", error);
});
});
}
mount(() => <StartClient />, document.getElementById("app")!);
// entry-server.tsx
import { createHandler, StartServer } from "@solidjs/start/server";
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My App</title>
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
// app.config.ts
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({
server: {
preset: process.env.NODE_ENV === "production"
? "node-server"
: "node-dev",
},
vite: {
server: {
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
},
},
});
src/
├── app.tsx # Isomorphic root component
├── entry-client.tsx # Browser entry point
├── entry-server.tsx # Server entry point
app.config.ts # Build configuration
Keep app.tsx isomorphic:
isServer checks if neededClient-only code in entry-client.tsx:
Server-only code in entry-server.tsx:
Configuration in app.config.ts:
Always wrap router with Suspense: