Rakkas js , What if nextjs used vite

tigawanna

Dennis kinuthia

Posted on April 9, 2023

Rakkas js , What if nextjs used vite

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;

Enter fullscreen mode Exit fullscreen mode

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,
    }
  );
Enter fullscreen mode Exit fullscreen mode

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"}
/>
Enter fullscreen mode Exit fullscreen mode

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

file structure

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
}
Enter fullscreen mode Exit fullscreen mode

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
admin page structure

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 };
  }
}

Enter fullscreen mode Exit fullscreen mode

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) });
      },
    }
  );
Enter fullscreen mode Exit fullscreen mode

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");
            },
        },
    );
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
  },
});

Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
tigawanna
Dennis kinuthia

Posted on April 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related