solid-start icon indicating copy to clipboard operation
solid-start copied to clipboard

Redirect doesn't early exit component when loading from browser address bar

Open aquaductape opened this issue 4 months ago • 3 comments

Duplicates

  • [X] I have searched the existing issues

Latest version

  • [X] I have tested the latest version

Current behavior 😯

When redirecting inside async server function call, the rest of the component still continue to run inside the server.

routes/admin.tsx

import { cache, createAsync, redirect } from "@solidjs/router";

const isAuthorized = false;

const getUser = cache(async () => {
  "use server";

  console.log("getUser: before redirect"); // logs
  if (!isAuthorized) {
    throw redirect("/about");
  }
  console.log("getUser: after redirect"); // doesn't log
  return {
    name: "John",
    age: 42,
  };
}, "getUser");

const Admin = () => {
  const res = createAsync(() => getUser());

  // server does unnecessary work here when redirecting
  console.log("Admin component"); // logs even after redirect, logs twice as well

  return (
    <div>
      <h1>admin</h1>
      <p>{res()?.name}</p>
    </div>
  );
};

Logs in server:

getUser: before redirect
Admin component
Admin component

Expected behavior 🤔

Basically for redirecting to exit early in the component execution, like how NextJS 14 works.

NextJS 14 app/admin/page.tsx

import { redirect } from 'next/navigation';

const isAuthorized = false;

const getUser = async () => {
  'use server';

  console.log('getUser: before redirect'); // logs
  if (!isAuthorized) {
    redirect('/about');
  }
  console.log('getUser: after redirect'); // doesn't log
  return {
    name: 'John',
    age: 42,
  };
};

const Admin = async () => {
  const res = await getUser();

  // server skips rest when redirecting
  console.log('Admin component'); // doesn't log

  return (
    <div>
      <h1>admin</h1>
      <p>{res?.name}</p>
    </div>
  );
};

Logs in server:

getUser: before redirect

Steps to reproduce 🕹

Steps:

  1. clone https://github.com/aquaductape/solid-start-redirect-test
  2. enter http://localhost:3000/admin in browser address bar
  3. redirects to /about
  4. see terminal logs

Context 🔦

No response

Your environment 🌎

System:
  OS: macOS 14.4.1 (23E224)
  Chip: Apple M1 Pro
Binaries:
  Node: v18.17.1
  npm: 10.1.0
npmPackages:
  solid-start: ^1.0.0-rc.0

aquaductape avatar Apr 23 '24 05:04 aquaductape

Note 1

When navigating to admin page via clicking Admin anchor tag, Screenshot 2024-04-22 at 10 32 11 PM

it works to my expectation, early exits component after redirect like NextJS

Note 2

If I remove the optional chain operator on res()?.name that's rendered inside Admin component jsx, it crashes on client because res() returns undefined. But if you navigate to admin page via browser addressbar, the crash is ignored (logs error in terminal in dev mode).

const Admin = () => {
  const res = createAsync(() => getUser());

  // server does unnecessary work here when redirecting
  console.log("Admin component"); // logs even after redirect

  return (
    <div>
      <h1>admin</h1>
      <p>{res().name}</p>
    </div>
  );
};

aquaductape avatar Apr 23 '24 05:04 aquaductape

This is by design to a certain degree. Our Async isn't blocking until where you read from the resource, not where you declare it. This avoids waterfalls that Next cannot. Similarly our resources don't throw and can be undefined. Again to be non-blocking. We are exploring space of doing blocking along the reactive graph rather than the components in Solid 2.0. But the short of this is Solid does not work like React and we need to work on our documentation.

The error behavior inconsistency on SSR + Hydration vs navigation is interesting. I'm gathering doesn't cause the server to error for some reason and then it gets skipped during hydration. That is probably worth further examination.

ryansolid avatar Apr 23 '24 16:04 ryansolid

You can avoid the rendering part by not showing the component until the resource is ready.

Add Show as below

const Admin = async () => {
  const res = await getUser();

  createEffect(()=>{
       if (res()) {
           // won't log as by the time the component loads and redirects this won't get here
           console.log('Client - Admin component'); 
       }
  });
  
  // will log, as the resource hasn't loaded to do the redirect yet
  console.log('Client and Server - Admin component'); 

  return (
    <Show when{res()}>   
	    <div>
	      <h1>admin</h1>
	      <p>{res?.name}</p>
	    </div>
	</Show>
  );
};

apatrida avatar May 23 '24 18:05 apatrida