Dennis kinuthia
Posted on April 9, 2023
RakkasJS
My Nextjs replacement ,
Rakkasjs is an underdog in the react meta-framework landscape that checks all the boxes i care about .
Nextjs is cool but it's webpack can get aggressive with systemm resources especially on windows machines , they have a replacement written in rust but i barely noticed a difference when i tried it maybe it's because it's still beta .
vite on the other hand amazes me in how good the dev experience is, their latest release is even more impressive
It has also become a favorite for most of the modern javascript frameworks and has a growing plugin eco-system that push it the next level
Rakkas builds on top of that and adds other tools/features by the author like
- hattip : Like express but with modern features
- a react query inspired data fetching solution
- file based router with nested layouts
- SSR and SSG
- multiple deployemnt options
- Node.js
- Static hosts
- Cloudflare Workers
- Netlify
- Vercel
- Deno
- Bun
- Lagon
to offer modern javascript fullstack features
Simple implementation
I made a simple real estate site with rakkas to try it out.
Pages/layouts
Rakkas is heavilly inspired by vite-plugin-ssr especially the file structure naming convention
In Rakkas, a page is a React component default exported from a module in the src/routes
directory with a name that matches *.page.jsx
(or tsx
).
Rakkas has a file system-based router. The file name determines the URL path. The rules are as you would expect:
Module name | URL path |
---|---|
src/routes/index.page.jsx |
/ |
src/routes/about.page.jsx |
/about |
src/routes/about/index.page.jsx |
also /about
|
so we'll first create a root layout to house everything
src/routes/layout.tsx
in which we can define UI elemnt we want globally on the entire app , like the toolbar,footer and in my case page transition progress-bar at the top
// This is the main layout of our app. It renders the header and the footer.
import {
ClientSuspense,
Head,
Layout,
useLocation,
UseLocationResult,
} from "rakkasjs";
import "../styles/tailwind.css";
import Toolbar from "../components/navigation/Toolbar";
import { ReactProgress } from "../components/shared/loaders/ReactProgress";
import { Footer } from "./../components/navigation/Footer";
const MainLayout: Layout = ({ children }) => {
const location = useLocation();
const isanumating = (location: UseLocationResult) => {
if (location.pending) {
return true;
}
return false;
};
return (
<>
{/* Rakkas relies on react-helmet-async for managing the document head */}
{/* See their documentation: https://github.com/staylor/react-helmet-async#readme */}
<Head title="Real estates">
<html lang="en" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</Head>
<header className="w-full h-12 p-2 z-30 sticky top-0 bg-slate-900 bg-opacity-20 text-slate-800 ">
{/* <Link /> is like <a /> but it provides client-side navigation without full page reload. */}
<ClientSuspense fallback="">
<Toolbar />
<ReactProgress isAnimating={isanumating(location)} />
</ClientSuspense>
</header>
<section className={" h-full w-full "}>{children}</section>
<footer className="footer flex flex-col md:flex-row items-center justify-center p-2">
<Footer />
</footer>
</>
);
};
export default MainLayout;
then we can nest our pages inside it src/routes/index.page.tsx
useServerSideQuery
To fetch the lsitsing to show on initial load we'll use
const { data, refetch } = useServerSideQuery(
() => return getPbListings(params);
{
refetchOnWindowFocus: true,
refetchOnReconnect: true,
}
);
useServerSideQuery
(useSSQ for short) is Rakkas's novel data fetching solution. You can think of it as Next.js's getServerSideProps on steroids. It lets you write code as if the server-client barrier didn't exist: You can call filesystem APIs, perform database queries, or call third-party APIs that require private keys or have CORS disabled. You can put any type of server-side code right in your component. Rakkas will strip this server-side code and its imports from the client bundle.Like
useQuery
,useSSQ
takes a query function. When it's executed on the server, the query function is called directly. But when it's executed on the client, Rakkas serializes all the values used in the query function body and sends them to the server. The server then deserializes the values, runs the query function, and serializes the return value to be sent back to the client. Unlike getServerSideProps, it can be used in any React component, not just pages. It also provides type inference.
as for loading states
All Rakkas data fetching methods use Suspense. You can handle loading states by wrapping parts of your component tree in Suspense and providing fallbacks
rakkas currently doesn't have an Image
component but i hacked a basic one together for this project and got good enough lighthouse scores with it GoodImage.tsx
<GoodImage
props={{ src: img_url as string, alt: land?.location }}
placeholderSrc={alt_img_url}
height={"200px"}
width={"300px"}
/>
am using pocketbase which will lazily generate thumbnails for images uploaded on request which you can use as the fallback before the higher quality image loads
dynamic routes
after loading our listings we'd want to view each one individially , so we define a dynamic route
and we'll fetch this listing by ID based on the param passed in
const OneListingPage: Page = function OneListingPage({ params }: PageProps) {
const [par_ams, SetParams] = useState<GetPbListingsParams>({
filter_id:params.id,
perPage: 3,
page: 1,
sort: "-created",
expand: "owner",
})
const { data, refetch } = useServerSideQuery(
() => {
if (typeof params.id !== "string") {
throw new Error("Invalid request , params.id must be of type string");
}
return getPbListings(par_ams);
},
{
refetchOnWindowFocus: true,
refetchOnReconnect: true,
}
);
// retun JSX here
}
also notice how we have to validate the types of the params being sent to the fetcher function which will run on the server
auth guards
And we might also want an admin only page to facilitate adding new listings and updating the listings status after they're off the market
which is where rakkas auth guards come in
add a $guard.ts
to the admin directory
to mark it off limits to unauthed users and redirect them to the login oage instead . rakkas has alot of middleware like this for even deeper customization
import { pb } from "../../utils/api/pocketbase";
import { LookupHookResult, PageContext } from "rakkasjs";
export function pageGuard(ctx: PageContext): LookupHookResult {
if (pb.authStore.model) {
return true;
} else {
const url = new URL("/auth", ctx.url);
url.searchParams.set("callbackUrl", ctx.url.pathname + ctx.url.search);
// console.log("url === ",url)
return { redirect: url };
}
}
mutation
Just like react-query rakkas has
useMutation
and rakkas' own useServerSideMutation
in my case i had a ton of client side validation logic including a map so i stuck to useMutation
const mutation = useMutation<PBListings, ListingFormInputs>(
(input) => saveListing(input),
{
onSuccess: () => {
setError({ name: "", message: "" });
},
onError: (err) => {
console.log("rakkas useMutaion error === ", err);
setError({ name: "main", message: concatErrors(err) });
},
}
);
but useServerSideMutition is as easy to use
example
const { data: currentUserName } = useServerSideQuery(() => getUserName(), {
key: "userName",
});
const [localUserName, setLocalUserName] = useState(currentUserName);
const queryClient = useQueryClient();
const mutation = useServerSideMutation(
() => {
setUserName(localUserName);
},
{
onSuccess() {
queryClient.invalidateQueries("userName");
},
},
);
deploy
Now we deploy by following the instructions on the respective deployment strategy listed , for example vercel requires an adapter
npm install -S @hattip/vercel-edge
and a vite config adjusument
import { defineConfig } from "vite";
import rakkas from "rakkasjs/vite-plugin";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
tsconfigPaths(),
rakkas({
adapter: "vercel", // or "vercel-edge"
}),
],
server: {
host: true,
},
});
After building with rakkas build
, you can deploy with vercel deploy --prebuilt
.
⚠ Vercel/netlify serveless functions don't have response streaming yet , but with vercel implementing thier own version an AWS lambda adding them officially it's goig to be added soon to rakkas so that we can enjoy the perks of react streaming on vercel/netlify non-edge platforms
⚠ Another thing to look out for is react-icons imports , where i've noticed it cause an issueif it doesnt end with index.js
import { FaPhone, FaWhatsapp, FaEnvelope } from "react-icons/fa/index.js";
instead of
import { FaPhone, FaWhatsapp, FaEnvelope } from "react-icons/fa";
helpfull resources :
i asked a few quertons to the mantainer on their discussions and you might find the responeses helpful too
btw , hosting it on vercel fixed the uncompressed text and cimage caching headers errors , if you go the NodeJS/Bun way you might need to configure nginx,caddy or similar
Posted on April 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.