Hey 👋 As you mentioned, above SSR example repo should let you do it. But to be honest, SSR is very very complex and doesn't give as much benefits.
What you can do is have 3 states: null | false | true.
If null, show skeleton loaders (or hide fully)
If false, show button X
If true, show button Y
By default you set it to null, and after 1-2s it switches to proper boolean.
Such UI can be seen in many big providers such as DigitalOcean, Railway, or even Facebook. It is much faster solution and user doesn't usually care too much. In the end, those authentication requests are usually very optimized, and in Appwrite they can resolve within <0.1s.
Here is quick showcase of how such client-side-rendered UI looks in Railway:
well right now if the userLoading is true I show the skeleton loeaders and after if user = true show button X and false button Y
But for example, to make private route, how can I do ?
Because we gonna see the private route (like during 0.5s) if we arent connected ?
Or I can make a loader
You can do the same logic, a skeletonal loader instead of the outlet.
Something like (pseudo code):
<Header />
{ isLoading ? <Loader /> : <Outlet /> }
<Footer />
I guess I have to add this condition in all private page right ? I cant use middlesware file (I don't really know middleware but I thought I understood that it was used to make private routes) ?
Maybe middlesware only works on server-side ?
Possibly, I am not too experience with Next.js. Cant you do that typical "use client" in there?
You could also do this in the layout file. That layout would then have array of routes that needs to be protected, so it knows when to check and when not to
(just wild guess from docs), you can do into your layout file and add getData method where you try to get account, and return something based on that.
Then, you add use client at the top, and in yoru component you const data = await getData()
With that, loading state should be handeled by Next.js.. Soo maybe white page, maybe pending redirect, not sure. But it should be some out-of-the-box behaviour.
Refference: https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
There is my user.tsx hook for get Auth (and other stuff like login, signup, etc...) :
"use client"
import React, {createContext, useContext, useState, useEffect } from 'react'
import { account, databases } from '@/utils/appwrite'
export interface UserState {
user: any;
userLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
signup: (email: string, name: string, username: string, password: string) => Promise<void>;
}
const defaultState: UserState = {
user: null,
userLoading: true,
login: async () => {},
logout: async () => {},
signup: async () => {},
}
// create the context
const UserContext = createContext<UserState>(defaultState);
// create the provider component
export const UserProvider = ({ children } : { children: any }) => {
const [ user, setUser ] = useState<null | any>(null);
const [ userLoading, setUserLoading ] = useState(true);
useEffect(() => {
const chekcUser = async () => {
try {
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
setUser(null)
} finally {
setUserLoading(false)
}
}
chekcUser();
}, [])
const login = async (email: string, password: string) => {
try {
await account.createEmailSession(email, password);
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
throw error
}
}
const logout = async () => {
try {
await account.deleteSession('current');
setUser(null);
} catch (error) {
throw error
}
}
const signup = async (
email: string,
name: string,
username: string,
password: string
) => {
try {
const {$id} = await account.create('unique()', email, password, name)
await account.createEmailSession(email, password);
await databases.createDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER),$id, {
"userId": $id,
"username": username
});
await account.createVerification(process.env.NEXT_PUBLIC_URL + '/verifyEmail')
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
throw error
}
}
return (
<UserContext.Provider value={{ user, userLoading, login, logout, signup }}>
{children}
</UserContext.Provider>
)
}
// create the custom hook
export const useUser = () => useContext(UserContext);
(Sorry for the length, I want to give a max of context :D)
But as I use a context to have the state of the user, in layout.tsx, the value of user does not update (so false by default) since it is not in the context
Hmm, it should work in layout, I think 🤔
If you do something like const userData = useUser(); and then useEffect(() => { console.log(userData); }, [userData]);, it should log everytime it changes
Buut I might easily be wrong, I haven't worked with react too much
If above works, from that useEffect you can change some kind of layout state (useState) between true/false so it knows if it should render outlet or not (or even redirect)
Considering this :
export const useUser = () => useContext(UserContext);
I guess useUser() hook update the value of user only in the UserContext
Might be the case. Would there be anything wrogn with wrapping your whole app inside user context? I am thinking about having 2-layer layout.
First one on /*, where you add user context.
Another one for /dashboard/*, that uses value from that user context.
This way you put all auth-protected pages under "dashboard" folder, and it will make them all protected by default, thanks to the second layout
U mean the RootLayout create the UserContext like as I currently have and another Layout for private route (like /account, /setting) and public route (for page who cant be seen by logged user like /login /signup) ?
Yes, pretty much.
But for simplicity, I would move /account and /settings to /dashboard/account /dashboard/settings. That way you only need to setup that /dashbaord layout to have protection, and you prevent code duplication
I dont have dashboard page actually but yeah I can find a name for all private route
Name is up to you /user, /app.. If they go to that page without going to sub-page, you can redirect.. For example, going to /dashboard, you can simply send user to /dashboard/account (always)
its probably a stupid question but I have this problem. I dont wanna show the reset password form if the token and the secret are no available. For example ive got this link :
http://localhost:3000/resetPassword?userId=6491ce764d6de085bc87&secret=a4a063c0f067ef4344ab7ce4ef3e365f64bf3c235e4f03c769c40def255b20f62a499d427f59d6a0805d557ac15d5cb8ad1f5e6d9c167d3067b72f259928b13ddaaada1cf45363c09b1bc9ec1ea3585a398a7855b4a0aa7103b9e4e640510323b77e4068c2dafa967825387745823ac3588f211c14f13acb8843641fac6ed290&expire=2023-06-22+16%3A00%3A17.687
But how check if the token and the secret are available without launch the .updateRecovery() function
Because even if I the expire value is on the time, if the token is false, the form will be displayed
Recommended threads
- database documents relationships
after migrating from 1.7.4 to 1.8, database get document and list documents is getting results but with no relationship values, the relationship attribute will...
- Getting CORS error when accessing websit...
When accessing my website I'm getting: (index):1 Access to font at 'https://assets.appwrite.io/fonts/inter/Inter-Regular.woff2' from origin 'https://mvp-site-ra...
- How to ByPass the Hostname restriction f...
Hello, Im hosting my React application in a stateless server environment, where the IP of the client keeps changing for the server. How do I bypass the hostname...