I'm trying to create a link to unsubscribe from a newsletter using Next.js. The idea is, an unsubscribe link can be attached to the newsletter, with a format like
https://www.example.com/newsletter?reason=unsubscribe&id=jFD8vb6Jy855EPiKZ7NTPReNv8HYDzb4
with query parameters: reason (unsubscribing) and the id of the unsubscriber. So, when the page detected the reason being "unsubscribe," it calls a Lambda function that deletes that user, selected by their id, from our database. This works locally, but when testing it live and clicking the unsubscribe hyperlink from the newsletter email, the query parameters disappear. Instead of showing the entire link with query parameters, what is displayed is just
https://www.example.com/newsletter,
so the Lambda function can't remove the subscriber from the mailing list. I have a feeling this has something to do with SSR with Next.js, but can't figure out how to fix it. Thanks!
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import styled from 'styled-components'
import Button from '../components/Button'
import Footer from '../components/Footer'
import Input from '../components/Input'
import NewsletterState from '../components/NewsletterState'
import Page from '../components/Page'
import StandardNavbar from '../components/StandardNavbar'
import { media } from '../utils/media'
interface EmailPayload {
email: string
}
export default function Newsletter () {
const Router = useRouter()
const [hasSuccessfullySentMail, setHasSuccessfullySentMail] = useState(false)
const [hasErrored, setHasErrored] = useState(false)
const { register, handleSubmit, formState } = useForm()
const { isSubmitSuccessful, isSubmitting, isSubmitted, errors } = formState
async function onSubmit (payload: EmailPayload) {
try {
const res = await fetch('/api/signUpNewsletter', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ...payload }),
})
if (res.status !== 204) {
setHasErrored(true)
}
} catch {
setHasErrored(true)
return
}
setHasSuccessfullySentMail(true)
}
const isSent = isSubmitSuccessful && isSubmitted
const isDisabled = isSubmitting || isSent
const isSubmitDisabled = Object.keys(errors).length > 0 || isDisabled
if (Router.query.reason === 'unsubscribe') {
async function unsubscribeNewsletter () {
await fetch('/api/unsubscribeNewsletter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: Router.query.id }),
})
}
unsubscribeNewsletter()
return (
<>
<Head>
<title>{'Unsubscribe'}</title>
<meta content="Unsubscribe from newsletter." />
</Head>
<StandardNavbar />
<Page
title='Unsubscribe Successful'
description="You will no longer receive our newsletter."
>
</Page>
<Footer />
</>
)
} else {
return (
<>
<Head>
<title>{'Newsletter'}</title>
<meta content="Sign up for our newsletter." />
</Head>
<StandardNavbar />
<Page
title="Newsletter"
description='Enter your email to get weekly updates!'
>
<MainDiv>
<Wrapper>
{hasSuccessfullySentMail ? (
<NewsletterState />
) : (
<Form
// @ts-expect-error
onSubmit={handleSubmit(onSubmit)}
>
{hasErrored && <ErrorMessage>Couldn't send email. Please try again.</ErrorMessage>}
<InputGroup>
<InputStack>
{errors.email && <ErrorMessage>Email is required</ErrorMessage>}
<Input placeholder='Email' id='email' disabled={isDisabled} {...register('email', { required: true })} />
</InputStack>
</InputGroup>
<SubmitButton as='button' type='submit' disabled={isSubmitDisabled}>
Sign Up!
</SubmitButton>
</Form>
)}
</Wrapper>
</MainDiv>
</Page>
<Footer />
</>
)
}
}
const SubmitButton = styled(Button)`
display: flex;
margin-top: 2rem !important;
margin: 0 auto;
`
const MainDiv = styled.div`
padding: 5rem;
`
const Wrapper = styled.div`
flex: 2;
${media('<=tablet')} {
text-align: center;
}
`
const Form = styled.form`
& > * {
margin-bottom: 2rem;
}
`
const InputGroup = styled.div`
display: flex;
align-items: center;
& > *:not(:last-child) {
margin-right: 2rem;
}
& > * {
flex: 1;
}
margin: 0 auto;
max-width: 25vw;
${media('<=tablet')} {
flex-direction: column;
& > *:not(:last-child) {
margin-right: 0rem;
margin-bottom: 2rem;
max-width: 90vw;
}
}
`
const InputStack = styled.div`
display: flex;
flex-direction: column;
& > *:not(:first-child) {
margin-top: 0.5rem;
}
`
const ErrorMessage = styled.p`
color: rgb(var(--errorColor));
font-size: 1.5rem;
`