This is the home of the Solid app framework. This is still a work in progress. Many features are missing or incomplete. Experimental status does not even mean beta status. Patch releases will break everything.
- File-system based routing
- Supports all rendering modes: Server-side rendering (SSR), Client-side rendering (CSR), Static Site Generation (SSG)
- Streaming
- Build optimizations with Code splitting, tree shaking and dead code elimination
- API Routes
- Built on Web standards: Fetch, Streams, WebCrypto
- Adapters for deployment to all popular platforms
- CSS Modules, SASS/SCSS Support
- Typescript-first
mkdir my-app
cd my-app
npm init solid@next
npm install
npm run dev
The monorepo uses pnpm
as the package manager. To install pnpm
, run the following command in your terminal.
npm install -g pnpm
Run pnpm install
to install all the dependencies for the packages and examples in your monorepo.
If you are using Solid Start within a monorepo that takes advantage of the package.json
"workspaces"
property (e.g. yarn workspaces) with hoisted dependencies (the default for yarn), you must include solid-start
within the optional "nohoist"
workspaces property.
- In the following, "workspace root" refers to the root of your repository while "project root" refers to the root of a child package within your repository
For example, if specifying "nohoist"
options from the workspace root (i.e. for all packages):
// in workspace root
{
"workspaces": {
"packages": [
/* ... */
],
"nohoist": ["**/solid-start"]
}
}
If specifying "nohoist"
options for a specific package using solid-start
:
// in project root of a workspace child
{
"workspaces": {
"nohoist": ["solid-start"]
}
}
Regardless of where you specify the nohoist option, you also need to include solid-start
as a devDependency in the child package.json
.
The reason why this is necessary is because solid-start
creates an index.html
file within your project which expects to load a script located in /node_modules/solid-start/runtime/entry.jsx
(where /
is the path of your project root). By default, if you hoist the solid-start
dependency into the workspace root then that script will not be available within the package's node_modules
folder.
Changed special compiled functions like server
, createServerData
, createServerAction$
, createServerMultiAction$
. to have a postfix $
to indicate their special compiled (hoisted behavior).
Also moved the optional first argument of createServerData$
under key
option. While this hides a very important option it makes the signatures more similar, so it is clear it is the main (first) function that is running on the server.
const data = createServerData$(
async pathname => {
let mod = mods[`./docs${pathname}.mdx`] ?? mods[`./docs${pathname}.md`];
return mod.getHeadings().filter(h => h.depth > 1 && h.depth <= 3);
},
{
key: () => path.pathname
}
);
- import solid from 'solid-start';
+ import solid from 'solid-start/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [solid()]
})
Why?
- We wanted to use the main entry point of
solid-start
for use within the app where you are spending most of your time. And for thevite
config, we use thesolid-start/vite
entrypoint.
import { createHandler, renderAsync, StartServer } from "solid-start/entry-server";
- export default createHandler(renderAsync(context => <StartServer context={context} />));
+ export default createHandler(renderAsync(event => <StartServer event={event} />));
- The prop received by
StartServer
, and given to you bycreateHandler
is calledevent
instead ofcontext
. It represents aPageEvent
which is aFetchEvent
that the server decided should be rendered by our components as aPage
. We adopted theevent
terminology to represent the input that our server handlers received. For example, the input to our top-level server handler is aFetchEvent
. It can then be routed to a server function and be passed as aServerFunctionEvent
or to an API Endpoint as anApiEvent
. This terminology is adopted from the ServiceWorker API and Cloudflare Workers API.
If you were using SSR:
- import { hydrate } from "solid-js";
- import { StartClient } from "solid-start/entry-client";
+ import { mount, StartClient } from "solid-start/entry-client";
- hydrate(() => <StartClient />, document);
+ mount(() => <StartClient />, document);
If you were not using SSR and rendering your app client-side:
- import { render } from "solid-js";
- import { StartClient } from "solid-start/entry-client";
+ import { mount, StartClient } from "solid-start/entry-client";
- render(() => <StartClient />, document.body);
+ mount(() => <StartClient />, document);
-
Earlier, you called
hydrate(document)
orrender(document.body)
here based on what kind of rendering mode you had selected and whether you had SSR turned on. We felt this was slightly annoying to change if you wanted to switch between the modes and error prone if you are not careful and end up passingdocument
torender
instead.We still wanted to expose
entry-client.tsx
to the user so that they can take over and do their own thing here if they want. We made a helper function calledmount
that embeds the logic for deciding how to interact with the app we get from the server, be ithydrate
orrender
.
// @refresh reload
import { Suspense } from "solid-js";
- import { Meta, Link, Routes, Scripts } from "solid-start/root";
+ import { FileRoutes, Scripts, Html, Head, Body, Routes, Meta, ErrorBoundary } from "solid-start";
export default function Root() {
return (
- <html lang="en">
+ <Html lang="en">
- <head>
+ <Head>
- <meta charset="utf-8" />
+ <Meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <Meta name="viewport" content="width=device-width, initial-scale=1" />
- <Meta /> // already exists inside `Head`
- <Links /> // already exists inside `Head`
- </head>
+ </Head>
- <body>
+ <Body>
<Suspense>
<ErrorBoundary>
<a href="/">Index</a>
<a href="/about">About</a>
- <Routes />
+ <Routes>
+ <FileRoutes />
+ </Routes>
</ErrorBoundary>
</Suspense>
<Scripts />
- </body>
+ </Body>
- </html>
+ </Html>
);
}
-
We changed how we declare our routes to make it more flexible. Earlier we gave you a
Routes
component fromsolid-start
that was equivalent to rendering aRoutes
from@solidjs/router
(yeah we know its confusing, that's why we are changing it) and filling it with the routes from the file system. The opt-in to the file-system routing was all-in or nothing. You didn't have an opportunity to add moreRoute
s. We now exportFileRoutes
fromsolid-start
that represents the route config based on the file-system. It is meant to be passed to theRoutes
component fromsolid-start
or wherever you want to use the file-system routes config.- You can use it together with other
Route
components.<Routes> <FileRoutes /> <Route path="/somewhere" component={SomeComponent} /> </Routes>
- Also for quickly starting an app without creating a bunch of files, you can define your routes in a single file. We generally don't recommend this since it's a good idea to code split your app along your routes, but its a neat trick.
<Routes> <Route path="/somewhere" component={SomeComponent} /> </Routes>
- You can use it together with other
-
For consistency between the SSR and client-side rendering modes, we needed to take more control of
root.tsx
specifically, we couldnt just take<html></html>
and<head></head>
tags and allow them to be part of the component tree since we can't client-side render the whole document. We only really get to take overdocument.body
. We needed to ship with specialHtml
,Head
, andBody
components that you use inroot.tsx
instead of the lower-case counterparts. These document flow components know what to do whether you are in SSR mode on or off. -
We can avoid you having to include
Meta
andLinks
fromsolid-start/root
in yourhead
since we do it by default. -
We will always use the title-case variants of the tags used in
head
(eg.Link
>link
,Style
>style
,Meta
>meta
) for consistency throughout the app -
solid-meta
is renamed to@solidjs/meta
-
solid-app-router
is renamed to@solidjs/router
-
solid-start
exports all the components meant to be used in your app and these components work on the client and server. Sometimes they are the same on both, and other times they coordinate between the two. -
Now, our
root.tsx
even more closely replicates how you would be writing yourindex.html
. And this was intentionally done so that we could enable an SPA mode for you that used the same code as the SSR mode without changing anything. How we do this? At build time for SPA mode, we quickly run the vite server, and make a request for your app's index and we tell ourBody
component not to render anything. So the index.html we get is the one you would have written. We then use thatindex.html
as your entrypoint. You can still write your ownindex.html
if you don't want to use this functionality.
export function routeData() {
- return createServerResource(async (_, { request }) => {
+ return createServerData$(async (_, { request }) => {
const user = await getUser(request);
if (!user) {
throw redirect("/login");
}
return user;
});
}
- Renamed
createServerResource
tocreateServerData$
, andcreateRouteResource
tocreateRouteData
: We renamedcreateServerResource
tocreateServerData$
because we were not using thecreateResource
signature and that was confusing and we needed to indicate the function was compiled. We just return one single signal fromcreateServerData$
instead of a tuple likecreateResource
does. And we have moved the source into the options askey
.
- const logoutAction = createServerAction(() => logout(server.request));
+ const [logginOut, logOut] = createServerAction$((_, { request }) => logout(request));
We pass in a ServerFunctionEvent
which has a request
field as the second argument to server actions. You can use this to access to the HTTP Request sent for your action and get the headers from it for things like auth.
We now return a tuple where the first argument is the current submission, and the second is the submit function it also has a progressive enhancible form attached to it logout.Form
.
export default function NotFound() {
return (
<div>
<HttpStatusCode code={404} />
<HttpHeader name="my-header" value="header-value" />
</div>
);
}
All credit for the work on Forms and Sessions goes to the @remix-run team, MIT License, Copyright 2021 Remix Software Inc.