-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NextJS #2146
NextJS #2146
Conversation
Notes so far: I think NextJS 12 is fine, I think it's working using the rust compiler (swc) for the app and babel for testing. Things that I have been going through and refactoring:
|
Is the plan to convert the react-snap pages to server-side render, then leave the rest of the app client-side rendered as before and change to server rendering as needed for this first step? Otherwise it will be a lot of code changes... |
Nothing will be server-rendered in this PR (but everything will be "snapped" ie. static export. Don't think you can do it halfway) |
How would that work for pages that require the user to be logged in first? Will it be static HTML with the loader, then React Query fetches data client-side once the app is hydrated? |
Yes |
2e6da02
to
436a162
Compare
@darrenvong this might be a good point to have a look and make some comments. Only login and dashboard are working, but I also moved across (and fixed type errors of) all the related dependencies (which are a lot!) and also components. I also managed to get all the tests for those files working locally, which took a lot of time replacing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn this is gonna be a big refactor 😅 not really sure how far in I am with the screwed up diffs (probably 40-50%) so will have to continue looking when I have more time.
What's the plan from this? Are we actually thinking of porting the whole app all in one go once the bumps and issues are ironed out?
How will this work for ops/CI as well? I guess instead of serving files from CloudFront in a S3 bucket, we'd be running this as a dockerised app instead and have nginx route to this... somehow? Curious as to what extra complexity it adds on that front as it looks simple for local development, but might not be the case for ops 😅
// It needs to be in the form dynamic(() => import("components/MarkdownNoSSR")) | ||
// This is hacky. Really we need to just ditch any non-ssr components | ||
/// TODO: Get an SSR-friendly markdown editor | ||
jest.mock("next/dynamic", () => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you don't mock this? Is this a somewhat known hack used by people to set up tests? 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't mock it, it just renders nothing for the component. "Well-known" I'm not sure, but it came up in some googling. Really I want to get rid of it asap by removing anything that doesn't support ssr.
app/web/features/communities/discussions/DiscussionsSection.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More comments/replies. Will have to continue looking when I get more time again
675e9a6
to
fb8edad
Compare
How you feel about setting up another environment and push this under another subdomain ( |
Good idea! |
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/couchers-org/couchers/FMctHu9jrjQciVpin35GHhAN9gwn |
@darrenvong this is kind of done. Some things aren't working in storybook and I need to figure out how to generate the protos on vercel but it seems to be working in general |
How do you see it in the Vercel preview? Or is that not fully working yet and only works locally? |
Also I'll try my best to review, but probably stick to more high level testing/poking around... I'm not sure it's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly questions, but looks good from what I've gone through so far. Will continue looking when I have more time
@@ -31,6 +32,9 @@ const useStyles = makeStyles((theme) => ({ | |||
export default function CookieBanner() { | |||
const classes = useStyles(); | |||
const [hasSeen, setHasSeen] = usePersistedState("hasSeenCookieBanner", false); | |||
// since we are using localStorage, make sure don't render unless mounted | |||
// or there will be hydration mismatches | |||
const isMounted = useIsMounted().current; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this change recently in React - facebook/react#22114 - I thought this hook might be a bit hacky (and maybe still is, only time will tell how compatible this will be with the newer React 18 concurrent patterns) but interesting to see it become useful again in the context of SSR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't like the fact it needs .current
, I tripped up by omitting that and then obviously isMounted
was always true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could probably change the useIsMounted
hook to return .current
within the hook result, but then I think there'll be a few places that use the useSafeState
hook will need updating. Based on the linked issue in the React repo, I think the useSafeState
should probably be removed and swapped to a normal useState
instead, as they acknowledged that warning is mostly a false positive if they have remove it.
|
||
await waitFor(() => { | ||
expect(date?.format().split("T")[0]).toEqual("2021-03-20"); | ||
expect(date?.format().split("T")[0]).toBe("2021-03-20"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought toEqual
and toBe
works the same when it's a primitive value like strings 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember if this was necessary or was just left behind from something else. Maybe the latter
containerPadding: { | ||
maxWidth: theme.breakpoints.values.md, | ||
paddingInlineStart: theme.spacing(4), | ||
paddingInlineEnd: theme.spacing(4), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do these change the look/layout of things with what's added in line 112 compared to before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't spread out as much at large breakpoints, but I think it looks good and the previous way doesn't work with hydration. If we want it to look closer to before, we could add media queries here.
@@ -20,26 +20,26 @@ function useAppContext<T>(context: Context<T | null>) { | |||
export default function AuthProvider({ children }: { children: ReactNode }) { | |||
const store = useAuthStore(); | |||
|
|||
const push = useHistory().push; | |||
const router = useRouter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you happen to know if router
is memoised/stable? Otherwise, wonder if it defeats the point of using the dependency array check in the useEffect
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right it's not: vercel/next.js#18127
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, isn't this right? Because if it's not stable, we don't want to accidentally call the "old" router.push
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but then we run into that infinite loop issue I think you faced in that other useEffect
in some cases so seems like a bug to me? It's unclear what bad things would happen if we call the "old" router.push
... I'd assume nothing since it doesn't have router state like react-router? 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a workaround taken from that bug thread which allows to use the latest router.push but doesn't trigger useEffect. I also found the t
dependency in useAuthStore
was causing that useEffect to fire too often too, so fixed that.
"successfully deployed" but:
Will fix the last two later. |
@@ -20,26 +20,26 @@ function useAppContext<T>(context: Context<T | null>) { | |||
export default function AuthProvider({ children }: { children: ReactNode }) { | |||
const store = useAuthStore(); | |||
|
|||
const push = useHistory().push; | |||
const router = useRouter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but then we run into that infinite loop issue I think you faced in that other useEffect
in some cases so seems like a bug to me? It's unclear what bad things would happen if we call the "old" router.push
... I'd assume nothing since it doesn't have router state like react-router? 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think there was anything major and so mostly questions here and there having gone through the boring stuff. But boring is good, as that means we kept most of the React code fairly intact, so nice job on that!
Only looked through the pages
/ routing stuff on an ad-hoc (as I mostly went from top to bottom to stop GitHub crapping out on this large diff), but I noticed now the routing logic has moved there instead of some of the Switch
stuff we had with React Router. Might it be worth having unit test for that logic? Or is it gonna be a pain since it's bundled within Next specific components now?
@@ -26,7 +26,7 @@ const Form = ({ setDate }: { setDate: (date: Dayjs) => void }) => { | |||
); | |||
}; | |||
|
|||
describe("DatePicker", () => { | |||
describe.skip("DatePicker", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be skipped still? Does that mean the DatePicker doesn't work well when server rendered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not for the life of me get this to work - as is currently, it hangs forever. I think it's to do with timezone mock and mocking the Date
implementation, but I never got it working.
Maybe we should follow your advice and ditch this anyway for native pickers...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah this datetime
mock seems pretty buggy and I never got it working outside of these tests... well I guess it just doesn't work if they start failing here too.
@@ -0,0 +1,3 @@ | |||
curl -L https://gitlab.com/couchers/couchers/-/jobs/artifacts/develop/download\?job\=protos -o /tmp/protos.zip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only for the preview environment? Or will we need it for production too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah still figuring this bit out
@@ -178,13 +179,15 @@ export default function Signup() { | |||
authActions.authError( | |||
isGrpcError(err) ? err.message : t("global:fatal_error_message") | |||
); | |||
history.push(signupRoute); | |||
router.push(signupRoute); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh this was what I'm trying to refer to in AuthProvider
- I'm guessing since we ignored router
as a dependency here and it's still fine (?), maybe it doesn't matter if we call the "old" router.push
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case was for the next-router-mock
router, not the real one. But see my other comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we be using the workaround here then, rather than leaving it out of the dependency array?
import { service } from "service"; | ||
import isGrpcError from "utils/isGrpcError"; | ||
|
||
export default function PagePage({ pageType }: { pageType: PageType }) { | ||
export default function PagePage({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol I hope we rename this "model" in some way so we don't have this PagePage
confusion...
@@ -45,7 +44,7 @@ export default function DiscussionsListPage({ | |||
...useDiscussionsListStyles(), | |||
...useStyles(), | |||
}; | |||
const hash = useLocation().hash; | |||
const hash = typeof window !== "undefined" ? window.location.hash : ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it not matter this defaults to ""
since it will be behind a loading spinner when rendered server side? How does it deal with the change of receiving #new
once it's at client side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the server just renders the header, spinner, and footer; and initially, so does the client.
const validateHasLocation = ( | ||
data: string | number | string[] | number[] | { value: null | unknown } | ||
) => { | ||
if (!data) return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this also covers data === ""
from before since "" is falsey?
@@ -29,8 +29,8 @@ const Template: Story<any> = (args) => { | |||
params={urlParams.current} | |||
/> | |||
<p> | |||
Pressing enter in the field shouldn't perform a submit action, but the | |||
button should. | |||
Pressing enter in the field shouldn't perform a submit action, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens on the server side without converting the character to an escape sequence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably nothing but there was a new lint rule complaining. Maybe I should just disable it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it doesn't matter that much as we should be moving these messages into JSON files too as part of the i18n work, so probably fine to leave it for now
app/web/next-i18next.config.js
Outdated
i18n: { | ||
defaultNS: "global", | ||
defaultLocale: "en", | ||
locales: ["en", "fr"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we should have "fr" in here until we have a decent amount of the app translated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was mostly for me to test :P
app/web/package.json
Outdated
"@types/css-mediaquery": "^0.1.1", | ||
"@types/geojson": "^7946.0.8", | ||
"@types/google-protobuf": "^3.15.5", | ||
"@types/jest": "^26.0.24", | ||
"@types/node": "^14.18.0", | ||
"@types/node": "16.11.6", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we upgraded to Node 16 on Vercel?
Also, loosely related, do we not need a Dockerfile for this anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not sure why I changed that
I am thinking about just ignoring that warning about the tabs. I think the behaviour is correct! What do you think? |
Yeah, otherwise I think a random white space will get highlighted if we add the empty string to the list of possible tabs, which would probably look more weird. I'm not sure anything bad would happen apart from no tabs get highlighted, which seems fine to me. Only thing I'm less sure of is having the "Messages" title displayed even for host requests, as that might be a bit confusing as those aren't just messages? But maybe it's not worth nitpicking over in this refactor if we are planning to re-do requests separately at some point anyway |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be the last thing hopefully!
<ReferenceForm | ||
referenceType={referenceType} | ||
userId={userId} | ||
step={step} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<ReferenceForm | |
referenceType={referenceType} | |
userId={userId} | |
step={step} | |
/> | |
<ReferenceForm | |
hostRequestId={hostRequestId} | |
referenceType={referenceType} | |
userId={userId} | |
step={step} | |
/> |
Otherwise host/surfer references are still broken
Another thing - Next seems to put things in a It might be worth running through their config wizard that supposedly set up a few things automatically? I think we'll miss some routing errors otherwise with how we set up at the moment. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's ship it and see how it goes 🚀
RIP