npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jk2908/solas

v0.2.3

Published

A React Server Components meta-framework powered by Vite

Downloads

629

Readme

Solas

Solas is a minimal React meta-framework powered by Vite, created for experimenting with routing, streaming, and prerendering with React Server Components.

It has not been rigorously tested yet (there are currently no automated tests) ... and broken behaviour should be expected.

Install

npm install @jk2908/solas react react-dom react-server-dom-webpack vite
npm install -D @vitejs/plugin-react typescript vite-tsconfig-paths

Use

Create a Vite config that registers Solas.

import { defineConfig } from 'vite'

import solas from '@jk2908/solas'
import react from '@vitejs/plugin-react'

export default defineConfig({
	plugins: [solas(), react()],
})

Structure

Put your routes in app/.

app/
	+layout.tsx
	+page.tsx
	+middleware.ts
	+loading.tsx
	+401.tsx
	+403.tsx
	+404.tsx
	+500.tsx
	about/
		+layout.tsx
		+page.tsx
	api/
		+endpoint.ts
	posts/
		[id]/
			+page.tsx

Use these filename conventions:

  • +layout.tsx: shared layout for a route branch.
  • +page.tsx: page component for a route.
  • +endpoint.ts: request handler for non-page routes.
  • +middleware.ts: middleware that runs for the current route branch and is inherited by child routes. Parent and child middleware stack together.
  • +loading.tsx: loading fallback inherited by child routes.
  • +401.tsx: boundary for unauthorised responses in the current route branch and its children.
  • +403.tsx: boundary for forbidden responses in the current route branch and its children.
  • +404.tsx: boundary for not found responses in the current route branch and its children.
  • +500.tsx: boundary for server errors in the current route branch and its children.

Nested folders create nested routes. Dynamic segments use [param], and catch-all segments use [...param].

Status boundaries follow the same override pattern as layouts: a child route uses the nearest matching boundary file above it, and a more specific boundary replaces a parent one.

Config

All Solas options are passed to solas() inside defineConfig.

url

url is optional. If you set it, Solas treats it as the public origin for your app.

Solas resolves it in this order:

  • the url option passed to solas()
  • VITE_APP_URL

Current behaviour:

  • Solas reads that value during plugin configuration.
  • Solas exposes the resolved value as import.meta.env.VITE_APP_URL.
  • If url is set, prerender uses it as the request origin for build-time renders.
  • The runtime router does not otherwise require config.url for routing to work.

In practice, you only need url if your app code wants to read the public origin from import.meta.env.VITE_APP_URL, or if your prerendered output needs a real public origin.

If you do want to set it explicitly, this is the shape:

export default defineConfig(({ mode }) => ({
	plugins: [
		solas({
			url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
		}),
	],
}))

If you prefer an environment variable, set this instead:

VITE_APP_URL=https://example.com

port

Use port to change the development server port.

Default: 8787

export default defineConfig({
	plugins: [
		solas({
			port: 4000,
		}),
	],
})

precompress

Use precompress to control whether Solas writes compressed build assets.

Default: true

export default defineConfig({
	plugins: [
		solas({
			precompress: false,
		}),
	],
})

prerender

Use prerender to set the default prerender mode for the app. Valid values are full, ppr, and false.

Default: false

  • false: do not prerender the route.
  • full: render the full route to HTML at build time.
  • ppr: prerender a static shell and defer dynamic regions to request time.
export default defineConfig({
	plugins: [
		solas({
			prerender: 'ppr',
		}),
	],
})

This value is only the default. Route files can override it with export const prerender = ..., and the nearest explicit export wins.

// vite.config.ts
export default defineConfig({
	plugins: [solas({ prerender: 'full' })],
})
// app/about/+layout.tsx
export const prerender = 'ppr'
// app/about/team/+page.tsx
export const prerender = false

In that example, the app default is full, the about layout overrides it to ppr, and the page overrides it again to false.

For dynamic routes, prerendering uses the params you export from the page:

export const params = () => [{ id: 'post-1' }, { id: 'post-2' }]
export const prerender = 'full'

In ppr mode, Solas prerenders the shell and lets you defer parts of the tree to request time.

Use dynamic() inside a Suspense boundary to mark a subtree as request-time only:

import { Suspense } from 'react'

import { dynamic } from '@jk2908/solas/server'

export const prerender = 'ppr'

export default function Page() {
	return (
		<Suspense fallback={<div>Loading...</div>}>
			<Ts />
		</Suspense>
	)
}

async function Ts() {
	dynamic()
	return <div>{Date.now()}</div>
}

During prerender, dynamic() suspends so the nearest Suspense fallback is written into the static shell. At request time, the deferred content resolves normally.

If you call dynamic() outside ppr mode, Solas does not defer that subtree. In full mode it logs a warning and the component still renders at build time.

headers(), cookies(), and url() also mark the current render path as dynamic, so they should be treated the same way when you are building a ppr shell.

metadata

Use metadata to set default document metadata.

export default defineConfig({
	plugins: [
		solas({
			metadata: {
				title: '%s - Solas',
				meta: [
					{
						name: 'description',
						content: 'My Solas app',
					},
				],
			},
		}),
	],
})

This is also only the default. Route metadata is merged in order, so config metadata can be extended or overridden by the shell, layouts, page, and status boundaries. The later, more specific route metadata wins for titles and duplicate tags.

// vite.config.ts
solas({
	metadata: {
		title: '%s - Solas',
	},
})

// app/+layout.tsx
export const metadata = {
	title: 'Docs',
}

// app/guides/+page.tsx
export const metadata = {
	title: 'Routing',
}

In that example, the final page title becomes Routing - Solas.

trailingSlash

Use trailingSlash to set the app-wide URL policy.

Default: never

  • never: /about/ redirects to /about
  • always: /about redirects to /about/
  • ignore: both forms resolve without a canonical redirect

This is a global setting in solas(). Solas does not read trailingSlash from route files.

Prerendered output follows the same policy. always writes route HTML as about/index.html, while never and ignore write it as about.html.

export default defineConfig({
	plugins: [
		solas({
			trailingSlash: 'always',
		}),
	],
})

sitemap

Use sitemap to generate a sitemap.xml at build time.

Default: false

When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via params. The origin for each URL comes from config.url.

export default defineConfig(({ mode }) => ({
	plugins: [
		solas({
			url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
			sitemap: true,
		}),
	],
}))

Routes with dynamic segments ([id]) or catch-all segments ([...param]) are only included if they export params and prerender.

To add routes that Solas cannot discover automatically (for example, catch-all routes backed by a CMS), pass an object with a routes function. The function receives the auto-discovered routes and returns the final list:

export default defineConfig(({ mode }) => ({
	plugins: [
		solas({
			url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
			sitemap: {
				async routes(existing) {
					const posts = await getPosts()
					return [...existing, ...posts.map(p => `/blog/${p.slug}`)]
				},
			},
		}),
	],
}))

The routes function can be async. The callback also lets you filter routes:

sitemap: {
	routes: (r) => r.filter(route => !route.startsWith('/admin')),
}

The sitemap is written to the build output directory as sitemap.xml after prerendering and before precompression.

logger.level

Use logger.level to control internal Solas logging.

Default: info

Valid values are debug, info, warn, error, and fatal.

  • debug: show everything
  • info: the default
  • warn: only warnings and errors
  • error: only errors
  • fatal: only fatal errors

This is mainly useful when debugging framework behaviour such as routing and prerendering. It is for Solas internals, not your app's general-purpose logging, and it does not control top-level CLI status output such as build and preview progress messages.

export default defineConfig({
	plugins: [
		solas({
			logger: {
				level: process.env.NODE_ENV === 'production' ? 'fatal' : 'info',
			},
		}),
	],
})

Scripts

Add scripts to your app:

{
	"scripts": {
		"dev": "solas dev",
		"build": "solas build",
		"preview": "solas preview"
	}
}

Commands

  • solas dev starts the development server.
  • solas build creates a production build, prerenders configured routes, and writes compressed assets when enabled.
  • solas preview serves the built app for local verification.