Issue 1:
Bug report

AMP pages include the query string in the canonical URL.

Describe the bug

On AMP pages the canonical link includes the query string. This has potential to cause duplicate content SEO issues on post pages, etc.

Using https://nextjs.org/blog/next-9 as an example:

https://nextjs.org/blog/next-9 (<link rel="canonical" href="/blog/next-9"/>)

https://nextjs.org/blog/next-9?utm_source=google (<link rel="canonical" href="/blog/next-9?utm_source=google"/>)

https://nextjs.org/blog/next-9?foo=bar (<link rel="canonical" href="/blog/next-9?foo=bar"/>)

In this example the query string does not affect the content, however each URL has a different canonical value.

To Reproduce

Visit an AMP page such as https://nextjs.org/blog/next-9 with and without query strings. Inspect the canonical link and note the inclusion of the query string.

Expected behavior

In the example above, the canonical link should not include the query string. However, this is nuanced, since in some situations including the query string (or a subset of the params) might be necessary when building the canonical link. For example, a URL such as /post?id=123456&foo=junk could potentially require a canonical link of <link rel="canonical" href="/post?id=123456"/> for correct SEO.

Ideally Next would allow full control over the canonical, i.e. it could be overridden. See #7706 for a similar feature request and #7373.

Screenshots

N/A

System information

Version of Next.js: 9

Additional context

We are using hybrid AMP rendering for our post pages. We already set our canonical for the non-AMP version, and there are lots of business and SEO concerns when it comes to canonical links (we are a media company). Having full control over the canonical in the AMP context would be incredibly useful for us.

Issue 2:
Hi, I am using next.js in my project and have integrated redux, redux-saga & reselect with it. I am dispatching actions in async getIntialProps but the component renders before the saga request attached to respective action is completed.

I have searched the issues of this repository and believe that this is not a duplicate.

Steps to Reproduce (for bugs)

I made an action and attached saga listener with it

I dispatched that action in async getInitialProps of pages/index.js

It should have waited before going to render of pages/index.js

However, the page rendered before the saga request got completed.

In this way, page renders before the store is updated with newly fetched data.

Context

Your Environment

Tech
Version

next
^4.1.4

node
>=6

OS
linux, windows

browser
all

etc

The code for certain issue is as follows:

pages/index

class Home extends React.Component {
constructor(props) {
super(props);
}
static async getInitialProps({store}) {
await store.dispatch(getSettings())
await store.dispatch(getAboutUs())
console.log('Team Before',store.getState().home.Team)
await store.dispatch(getTeam())
console.log('Team After',store.getState().home.Team)
await store.dispatch(getServices())
await store.dispatch(getTestimonials())
await store.dispatch(getPortfolio())
await store.dispatch(getPortfolioSettings())
}
render() {
return (
<HomeContainer />
);
}
}
export default withReduxSaga(Home)

Home Container

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import Home from 'components/views/Home';
import { Router } from 'routes';
import Root from 'containers/Root'
import LinearLoader from 'components/widgets/Loaders/LinearProgressLoader'

import {
getSettings,
getAboutUs,
getTeam,
getServices,
getPortfolio,
getPortfolioSettings,
getTestimonials
} from './actions';

import {
selectSettings,
selectGetSettingsStatus,
selectAboutUs,
selectGetAboutUsStatus,
selectTeam,
selectGetTeamStatus,
selectServices,
selectGetServiceStatus,
selectTestimonials,
selectGetTestimonialsStatus,
selectPortfolios,
selectGetPortfoliosStatus,
selectPortfolioSettings,
selectGetPortfolioSettingsStatus
} from './selectors';

export class HomeContainer extends React.Component {

changeRoute = (route) => {
Router.replaceRoute(route);
}

render() {
console.log("Settings", this.props.Settings)
const { GetPortfolioSettingsStatus, GetSettingsStatus, GetAboutUsStatus, GetTeamStatus, GetServicesStatus, GetTestimonialStatus, GetPortfoliosStatus } = this.props;
if(GetSettingsStatus.get('loading')) {
return (
<LinearLoader />
);
}
return (
<div>
{!!this.props.Settings && <Root Settings={this.props.Settings} isHome SettingsStatus={GetSettingsStatus} >
<Home
Settings={this.props.Settings}
SettingsStatus={GetSettingsStatus}
changeRoute={this.changeRoute}
Team={this.props.Team}
TeamStatus={GetTeamStatus}
AboutUs={this.props.AboutUs}
AboutUsStatus={GetAboutUsStatus}
Services={this.props.Services}
ServicesStatus={GetServicesStatus}
Testimonials={this.props.Testimonials}
TestimonialStatus={GetTestimonialStatus}
Portfolios={this.props.Portfolios}
PortfoliosStatus={GetPortfoliosStatus}
PortfolioSettings={this.props.PortfolioSettings}
PortfolioSettingsStatus={GetPortfolioSettingsStatus}
/>
</Root>}
</div>
);
}
}

const mapDispatchToProps = (dispatch) => {
return {
onGetSettings: bindActionCreators(getSettings, dispatch),
onGetAboutUs: bindActionCreators(getAboutUs, dispatch),
onGetTeam: bindActionCreators(getTeam, dispatch),
onGetServices: bindActionCreators(getServices, dispatch),
onGetPortfolio: bindActionCreators(getPortfolio, dispatch),
onGetTestimonial: bindActionCreators(getTestimonials, dispatch),
onGetPortFolioSettings: bindActionCreators(getPortfolioSettings, dispatch)
}
}

const mapStateToProps = createStructuredSelector({
Settings: selectSettings(),
Team: selectTeam(),
AboutUs: selectAboutUs(),
Services: selectServices(),
Testimonials: selectTestimonials(),
Portfolios: selectPortfolios(),
PortfolioSettings: selectPortfolioSettings(),
GetSettingsStatus: selectGetSettingsStatus(),
GetTeamStatus: selectGetTeamStatus(),
GetAboutUsStatus: selectGetAboutUsStatus(),
GetServicesStatus: selectGetServiceStatus(),
GetTestimonialStatus: selectGetTestimonialsStatus(),
GetPortfoliosStatus: selectGetPortfoliosStatus(),
GetPortfolioSettingsStatus: selectGetPortfolioSettingsStatus()
});

export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer)

Sagas

import es6promise from 'es6-promise'
import 'isomorphic-fetch'
import request from 'common/request'
import config from 'config/config'
import {
getSettingsSuccess,
getSettingsFail,
getAboutUsSuccess,
getAboutUsFail,
getServicesSuccess,
getServicesFail,
getPortfolioSuccess,
getPortfolioSettingsSuccess,
getPortfolioSettingsFail,
getPortfolioFail,
getTeamSuccess,
getTeamFail,
getTestimonialsSuccess,
getTestimonialsFail
} from './actions'

import {
GET_SETTINGS,
GET_ABOUT_US,
GET_SERVICES,
GET_PORTFOLIO,
GET_TEAM,
GET_TESTIMONIALS,
GET_PORTFOLIO_SETTINGS
} from './constants';

es6promise.polyfill()

function * getSettings () {
const settings = yield call(request, config.settings_type);
console.log('Settings Fetched: ', settings)
if(!settings) {
yield put(getSettingsFail('No Data Found'));
}
else if(!settings.err) {
yield put(getSettingsSuccess(settings[0]));
} else {
yield put(getSettingsFail(settings.err.reason));
}
}

function * getAboutUs () {
const aboutUs = yield call(request, config.about_us_type);
if(!aboutUs) {
yield put(getAboutUsFail('No Data Found'));
}
else if(!aboutUs.err) {
yield put(getAboutUsSuccess(aboutUs[0]));
} else {
yield put(getAboutUsFail(aboutUs.err.reason));
}
}

function * getServices () {
const services = yield call(request, config.services_type);
if(!services) {
yield put(getServicesFail('No Data Found'));
}
else if(!services.err) {
yield put(getServicesSuccess(services[0]));
} else {
yield put(getServicesFail(services.err.reason));
}
}

function * getPortfolio () {
const portfolio = yield call(request, config.portfolios_type);
if(!portfolio) {
yield put(getPortfolioFail('No Data Found'));
}
else if(!portfolio.err) {
yield put(getPortfolioSuccess(portfolio));
} else {
yield put(getPortfolioFail(portfolio.err.reason));
}
}

function * getPortfolioSettings () {
const portfolio_settings = yield call(request, config.portfolios_settings_type);
if(!portfolio_settings) {
yield put(getPortfolioSettingsFail('No Data Found'));
}
else if(!portfolio_settings.err) {
yield put(getPortfolioSettingsSuccess(portfolio_settings[0]));
} else {
yield put(getPortfolioSettingsFail(portfolio_settings.err.reason));
}
}

function * getTeam () {
const team = yield call(request, config.team_type);
// console.log('teams Fetched: ', team)
if(!team) {
yield put(getTeamFail('No Data Found'));
}
else if(!team.err) {
yield put(getTeamSuccess(team[0]));
} else {
yield put(getTeamFail(team.err.reason));
}
}

function * getTestimonials () {
const testimonials = yield call(request, config.testimonials_type);
if(!testimonials) {
yield put(getTestimonialsFail('No Data Found'));
}
else if(!testimonials.err) {
yield put(getTestimonialsSuccess(testimonials[0]));
} else {
yield put(getTestimonialsFail(testimonials.err.reason));
}
}

export function* homeSagas() {
yield fork(takeLatest, GET_SETTINGS, getSettings);
yield fork(takeLatest, GET_ABOUT_US, getAboutUs);
yield fork(takeLatest, GET_SERVICES, getServices);
yield fork(takeLatest, GET_PORTFOLIO, getPortfolio);
yield fork(takeLatest, GET_PORTFOLIO_SETTINGS, getPortfolioSettings);
yield fork(takeLatest, GET_TEAM, getTeam);
yield fork(takeLatest, GET_TESTIMONIALS, getTestimonials);
}

export default homeSagas;

Home Component

import ScrollableAnchor from 'react-scrollable-anchor'
import { Router } from 'routes';
import AboutUs from './AboutUs'
import Services from './Services'
import Team from './Team'
import Testimonials from './Testimonials'
import Portfolios from './Portfolio'

class Home extends React.Component {
constructor (props) {
super(props)
}

render() {
console.log('Teams available: ', this.props.Team)
return (
<div>
<div className="page-header" filter-color="orange">
{!!this.props.Settings && <div className="page-header-image" data-parallax="true" style={{backgroundColor: "grey", backgroundImage: `url(${this.props.Settings.getIn(['metadata','home_banner_image','url'])}`}} />}
<div className="container">
{
this.props.SettingsStatus.get('loaded') ?
<div className="content-center brand">
<img src={!!this.props.Settings ? this.props.Settings.getIn(['metadata','company_logo','url']): ''} alt={this.props.Settings.getIn(['metadata','agency_name'])} />
<h3>{!!this.props.Settings ? this.props.Settings.getIn(['metadata','punch_line']): 'Your Punch Line Here'}</h3>
</div>
:
<div className="content-center brand">
<h1> Loading ... </h1>
</div>
}
</div>
</div>
<div className="main">
{
this.props.AboutUsStatus.get('loading') ?
<div></div>
: this.props.AboutUsStatus.get('loaded') ?
<ScrollableAnchor id={'aboutUs'}>
<AboutUs aboutUs={this.props.AboutUs} changeRoute={this.props.changeRoute} />
</ScrollableAnchor>
: <div></div>
}

{
this.props.ServicesStatus.get('loading') ?
<div></div>
: this.props.ServicesStatus.get('loaded') ?
<ScrollableAnchor id={'services'}>
<Services services={this.props.Services} />
</ScrollableAnchor>
: <div></div>
}

{
this.props.TeamStatus.get('loading') ?
<div></div>
: this.props.TeamStatus.get('loaded') ?
<ScrollableAnchor id={'team'}>
<Team team={this.props.Team} />
</ScrollableAnchor>
: <div></div>
}

{
this.props.TestimonialStatus.get('loading') ?
<div></div>
: this.props.TestimonialStatus.get('loaded') ?
<ScrollableAnchor id={'testimonials'}>
<Testimonials testimonials={this.props.Testimonials} />
</ScrollableAnchor>
: <div></div>
}

{
this.props.PortfoliosStatus.get('loading') ?
<div></div>
: this.props.PortfoliosStatus.get('loaded') && this.props.PortfolioSettingsStatus.get('loaded') ?
<ScrollableAnchor id={'portfolio'}>
<Portfolios portfolios={this.props.Portfolios} settings={this.props.PortfolioSettings} changeRoute={this.props.changeRoute} />
</ScrollableAnchor>
: <div> </div>
}
</div>

</div>
);
}
}
export default (Home)