æ£åœãªããŒãžã®ãªã³ã¯ã®æ«å°Ÿã®ã¹ã©ãã·ã¥ã¯ã¯ã©ã€ã¢ã³ãåŽã®ããã²ãŒã·ã§ã³ã§ã¯æ©èœããŸããããã³ãã«ãèŠã€ããããããŒããªãã¬ãã·ã¥ã§404ã«ãªããŸãïŒssrïŒ
ã¿ã€ãã«ãããã«æ確ã«ããå¿ èŠãããå Žåã¯ãç¥ãããã ããã
é¢é£ãããã¹ãŠã®åé¡ã¯ã6ã«ããªã¢ã§ä¿®æ£ãããïŒããã§ã¯ãªããšæããŸãïŒãããµãŒãã®æ¹åïŒããããæ¬çªã®éçãšã¯ã¹ããŒãã§ã®ã¿åœãŠã¯ãŸããŸãïŒã«ãã£ãŠä¿®æ£ããããšããçç±ã§ã¯ããŒãºãããŸããã
æ¢åã®ããã°ãnext.jsã«æžãçŽããŠããã以åã¯æ«å°Ÿã®ã¹ã©ãã·ã¥ã䜿çšããŠããŸããã next.jsãå©çšããããã°ãäœæããããææ°ã®serve
ã圹ã«ç«ã¡ãŸãã ããããdev envãä¿®æ£ããã«ã¯ãæ«å°Ÿã®ã¹ã©ãã·ã¥ãåé€ããŠãprodã§301 Moved Permanently
ã䜿çšããå¿
èŠããããŸãã ãŸãã¯ãéçºè
ã®æ«å°Ÿã®ã¹ã©ãã·ã¥ãµããŒããå£ããŠããç¶æ
ã§ã©ã€ãããŸãã
åçŸå¯èœãªæå°éã®ã±ãŒã¹ã¯æ¬¡ã®ãšããã§ãïŒåçŸãªããžããªãžã®ãªã³ã¯ã¯ã¹ããããã®äžã«ãããŸãïŒã
// pages/index.js
import Link from "next/link";
export default () => (
<Link href="/about/">
<a>About</a>
</Link>
);
// pages/index.js
export default () => "about";
æå°éã®åçŸå¯èœãªãªããžããªhttps://github.com/iamstarkov/next.js-trailing-slash-bug-demo
git clone https://github.com/iamstarkov/next.js-trailing-slash-bug-demo
cd next.js-trailing-slash-bug-demo
yarn
yarn dev
http://localhost:3000/_next/static/development/pages/about.js
ã200edãããŠããããšã確èªããŸãhttp://localhost:3000/_next/on-demand-entries-ping?page=/about/
ã200edãããŠããããšã確èªããŸãhttp://localhost:3000/about/
ã404ãããŠããã®ã芳å¯ããŸãhttp://localhost:3000/about/
ã解決ããããã®æ°žç¶çãªè©Šã¿ã芳å¯ããŸãClient pings, but there's no entry for page: /about/
/about/
ã¯404 not found
ãšããŠè§£æ±ºãããã¹ãã§ã¯ãããŸãã/about/
ã¯200 ok
ãšããŠè§£æ±ºããå¿
èŠããããŸãClient pings, but there's no entry for page: /about/
å°å·ããªãã§ãã ãã/about
ãš/about/
ã¯ã©ã¡ããåãããã«æ©èœããã¯ãã§ã該åœãªã
ããã«åé¡ã«é¢ããä»ã®ã³ã³ããã¹ããè¿œå ããŸãã
https://github.com/zeit/next.js/blob/459c1c13d054b37442126889077b7056269eeb35/server/on-demand-entry-handler.js#L242-L249ã§ãã®ã³ãŒããå€æŽããå Žå
ãŸãã¯ããŒã«ã«ã§node_modules/next/dist/server/on-demand-entry-handler.js
const { query } = parse(req.url, true)
const page = normalizePage(query.page)
+ console.log('query.page', query.page);
+ console.log('page', page);
+ console.log('Object.keys(entries)', Object.keys(entries));
const entryInfo = entries[page]
// If there's no entry.
// Then it seems like an weird issue.
if (!entryInfo) {
const message = `Client pings, but there's no entry for page: ${page}`
next dev
ãåèµ·åãã httpïŒ// localhost ïŒ3000 /ãéããŠã[about]ãªã³ã¯ãã¯ãªãã¯ããŸãã
/about
query.page /about
page /about
Object.keys(entries) [ '/', '/about' ]
/about/
ïŒ
query.page /about/
page /about/
Object.keys(entries) [ '/', '/about' ]
Client pings, but there's no entry for page: /about/
åé¡ïŒå°ãªããšããã®äžéšïŒã¯ãããŒãžã®æ«å°Ÿã«ã¹ã©ãã·ã¥ãããå ŽåãonDemandEntryHandlerã®ããã«ãŠã§ã¢ããšã³ããªå ã®ããŒãžãèŠã€ããããªãããšã«ãããšæããŸãã
2æéã®èª¿æ»ãšæºåãããã®åé¡ã®è§£æ±ºã«åœ¹ç«ã€ããšãé¡ã£ãŠããŸãã
æãé¢é£æ§ããã泚ç®ãã¹ãåé¡ã¯ïŒ1189ãšïŒ3876ã§ã
ãããæçµçã«è§£æ±ºãããã®ã楜ãã¿ã«ããŠããŸãïŒ @timneutkens Next 7ã®æ«å°Ÿã®ã¹ã©ãã·ã¥ã®åé¡ã®ã¹ããŒã¿ã¹ã¯ã©ããªã£ãŠããŸããïŒ
@NathanielHill next @ 7ã§åçŸã§ããŸã
ç§ã¯nextjs7ã䜿çšããŠãããæ«å°Ÿã®ã¹ã©ãã·ã¥ã¯devãšprodã®äž¡æ¹ã§404ãçæããŠããŸãã
ãããŠåœ±é¿ïŒ
æ«å°Ÿã®ã¹ã©ãã·ã¥ãåé€ããã ãã§åé¡ã解決ããŸãã
æ«å°Ÿã®ã¹ã©ãã·ã¥ã¯ããªã³ã¯ã貌ãä»ããããå¯èœæ§ã®ãããã©ãŠã¶ããµãŒããŒããã®ä»ã®ãµãŒãã¹ã«ãã£ãŠè¿œå ãããããšãå€ããããå éšãªã³ã¯ãå¶åŸ¡ããããšã¯ã§ããŸãããå€éšãŠãŒã¶ãŒãã©ã®ãªã³ã¯ã«å°éããããå¶åŸ¡ããã®ã¯å°é£ã§ãã
ããŒãžã§ã³7ã§ããã®åé¡ãçºçããŠããŸãããããé¢é£ãããã©ããã¯ããããŸããããããNext.jsãããžã§ã¯ããå¥ã®Nowãããã€ã¡ã³ãã®ãµããã©ã«ããŒã«ãšã€ãªã¢ã¹ããŠããŸãã ãããã£ãŠãããŒã¹URLã¯primer.style
ããã primer-components.now.sh
Next.jsã¢ããªãprimer.style/components
ãšã€ãªã¢ã¹ããŠããŸãã æ¬çªç°å¢ã§ã¯ã primer.style/components
ã®ã€ã³ããã¯ã¹ããŒãžã¯æ£åžžã«æ©èœããŸããã primer.style/components/
ã¯404ãçæããŸãã
ãã®åé¡ãèŠã€ããã«ã¯ãå°ãæ€çŽ¢ããå¿ èŠããããŸããã ç§ã¯Netlifyã§éçãããã€ã¡ã³ãã䜿çšããŠããã®ã§ãprodã®åé¡ã§ã¯ãããŸããããéçºïŒæ¬¡ã®7ïŒã§ã¯ãæ«å°Ÿã«ã¹ã©ãã·ã¥ããããšã³ã³ãã€ã«ãããªãŒãºããã ãã§ããã®çç±ãç解ããã®ã¯å°é£ã§ããã ããïŒéçºç°å¢ã§æ«å°Ÿã®ã¹ã©ãã·ã¥ãåŠçããªãïŒã¯è¯ãDXã§ã¯ãªããšæããŸãã
ç§ããã®åé¡ãæ±ããŠããŸãããããŠããã¯æ¬åœã«è¿·æã§ããç§ã¯ãããããã«ä¿®æ£ãããããšãé¡ã£ãŠããŸãã
æ«å°Ÿã®ã¹ã©ãã·ã¥ãå¿
èŠãªå Žåã¯ããããè¡ãããšãã§ããŸãã <Link href='/about' as='/about/'><a>about</a></Link>
ã§ãããéææ¥/次ã®ã«ãŒãã䜿çšããŠããå Žåãããã¯äžå¯èœã§ãã ã ããç§ã¯ããªããå°éå
·ãšããŠtrailingSlash
ãè¿œå ã§ãããã©ãŒã¯ãæã£ãŠããŸãã ã圹ã«ç«ãŠãã°
æ«å°Ÿã®ã¹ã©ãã·ã¥ãå¿ èŠãªå Žåã¯ããããè¡ãããšãã§ããŸãã
<Link href='/about' as='/about/'><a>about</a></Link>
ã§ãããéææ¥/次ã®ã«ãŒãã䜿çšããŠããå Žåãããã¯äžå¯èœã§ãã ã ããç§ã¯ããªããå°éå ·ãšããŠtrailingSlash
ãè¿œå ã§ãããã©ãŒã¯ãæã£ãŠããŸãã ã圹ã«ç«ãŠãã°
@aluminickç³ãèš³ãããŸãããããããè©Šãããšããã
ãŸããïŒ6664ãïŒ6752ããããã®å©ãã«ã¯ãªããŸããããªããªãã experimental.exportTrailingSlash
ã¯next export
å°çšã§ããããã圹ã«ç«ããªãããã§ãã
@Janpotã«ããææãªãã«ãªã¯ãšã¹ãïŒ6421ããã
@iamstarkovãã®åé¡ã®ç¶æ³ã¯ã©ããªã£ãŠããŸããïŒ server.js
ããã¯ä»¥å€ã®è§£æ±ºçã¯ãããŸããïŒ
@dryleafã¹ããŒã¿ã¹ïŒãŸã éããŠããŸã
åæ§ã®åé¡...è€æ°ã®ã¹ã©ãã·ã¥ãè¿œå ãããå Žåã®ãªãã€ã¬ã¯ãã äŸïŒ https ïŒ
GitHubã®URLã¯é¢ä¿ãããŸãã
@iamstarkovã©ãããæå³ãããããªãã ããããå ã®æçš¿ãèªã¿çŽããåŸã¯ããã£ãšæ確ã«ã§ããããã§ãã
GitHubã®URLã¯ãã¢ããªãNext.jsã§ãã«ãããããšãã«URLãïŒã§ããã°ïŒã©ã®ããã«æ©èœããããç°¡åã«ç€ºãããšãç®çãšããŠããŸãã ã€ãŸãããŠãŒã¶ãŒãã¹ã©ãã·ã¥ãè¿œå ããŠããURLã¯æ©èœããã¯ãã§ãã
nextjs 9ã®ã¢ããããŒãã¯ãããŸããïŒ
Nextã¯åããŠã§ããããã®åé¡ã§äœ¿çšããŠããåé¿çã¯äœã§ããïŒ
@iamstarkovãã®åé¡ã®ç¶æ³ã¯ã©ããªã£ãŠããŸããïŒ
ãã®åé¡ãçŽ1幎éããŸã£ãã解決ãããªãã£ãããšã«ã·ã§ãã¯ãåããŸããã
Next.jsããŒã ã¯ããããä¿®æ£ãå§ããããã«ä»ã®çç±ãå¿
èŠã§ããïŒ
URLã¯ãæ«å°Ÿã®ã¹ã©ãã·ã¥ã«é¢ä¿ãªãæ©èœããã¯ãã§ãã Webäžã®ä»»æã®ãµã€ãã確èªããŠãã ããã
ãããNext.jsã®ç¯å²å€ã§ããå Žåã¯ãNowã§ãããæ§æããæ©èœãæäŸããŠãã ããã
ZeitããŒã ããã®ãããªé倧ãªåé¡ãäœå¹Žãç¡èŠããŠããããšã«ç§ã¯æ¬åœã«æ··ä¹±ããŠããŸãã
@exentrichããã¯ãã¹ã©ãã·ã¥ãªãã§ãã¹ãŠã®æ«å°Ÿã®ã¹ã©ãã·ã¥ãåãã«ãŒãã«301ãªãã€ã¬ã¯ãããã ãã§ã
now.json
ïŒ
"routes": [
{
"src": "/(.*)/",
"status": 301,
"headers": { "Location": "/$1" }
},
...
]
ãã ãããããNext.jsèªäœã«ãã£ãŠåŠçãããªãçç±ãšãããŒã ããã®åé¡ãç¡èŠããçç±ãããããŸããã
ããã¯ãïŒäœæ¥äžã®ïŒ public/
ãšãšãã«ãCRAå€æãå®è¡ãããŠããã®ãç®ã«ããäž»ãªåé¡ã§ãã
@rauchg
@NathanielHillããããšãïŒ
ãã®ãœãªã¥ãŒã·ã§ã³ãè©ŠããŸããããã¯ãšãªãã©ã¡ãŒã¿ãåé€ãããŠããŸãã ããšãã°ã /some/?query=1
ã¯ã¯ãšãªãªãã§/some
ãªãã€ã¬ã¯ããããŸãã ããªãã¯ãããä¿®æ£ããæ¹æ³ãç¥ã£ãŠããŸããïŒ
ãããããã¯åé¡ã®ããã«èãããŸã@exentrich
æ£èŠè¡šçŸã®åšãã«æé»ã®^
ãš$
ã©ãããããŠãããšèšãããŠããã®ã§ããã®åäœãæšæž¬ããããšã¯ã§ããŸããã§ããïŒã€ãŸããäŸãäžèŽããŸããïŒã ã¯ãšãªæååã«ç¬èªã«ã¢ã¯ã»ã¹ããŠè¿œå ãçŽãæ¹æ³ããããããããŸããïŒman_shruggingïŒé 匵ã£ãŠãã ãã
ã«ã¹ã¿ã ãšã¯ã¹ãã¬ã¹ãµãŒããŒãšavinoamr / connect-slashesã䜿çšããŠåäœãããããšããŠããŸãããåãåé¡ãçºçããŠããããã§ã
ããã¯ç¢ºãã«å€§ããªåé¡ã§ããç¹ã«/
ã«ãŒãããšã©ãŒããŒãžãã¹ããŒããSEOïŒNextã®äž»èŠãªé
åã®1ã€ïŒãå·ã€ããããã§ãã
301ãªãã€ã¬ã¯ããšã«ã¹ã¿ã ãšã¯ã¹ãã¬ã¹ãµãŒããŒã¯ãã¹ãŠãä¿®æ£ã§ã¯ãªããããã³ã°ã®ããã§ãã ç§ã®å Žåãã«ã¹ã¿ã ExpressãµãŒããŒã䜿çšããã«Nextã§å®å šã«æ©èœããã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŠ
PSïŒç§ã¯Nextâ€ïžãšäžç·ã«ä»äºãããã®ã倧奜ãã§ã
ããã¯ç¢ºãã«å€§ããªåé¡ã§ããç¹ã«
/
ã«ãŒãããšã©ãŒããŒãžãã¹ããŒãããããSEOãå·ã€ããããã§ãã
ããã¯ããªãã®SEOãå·ã€ããŸããã ã°ãŒã°ã«ã¯æ«å°Ÿã®ã¹ã©ãã·ã¥ãå¥ã®ããŒãžãšããŠæ±ããŸãã 404ã䜿çšããŠãããµã€ãå ã®ä»ã®ååšããªãããŒãžãããSEOã«åœ±é¿ãäžããããšã¯ãããŸããã ãã®äžãæ«å°Ÿã®ã¹ã©ãã·ã¥ã§ãªã³ã¯ããªãéããã°ãŒã°ã«ã¯ãããããããã¯ããŒã«ããããšã¯ããŸããã ãã®åé¡ã¯ãŸã æå¹ãªåé¡ã§ãããçãããèããŠããã»ã©éèŠã§ã¯ãããŸããã
@ nik-john @NathanielHill @dkrish @exentrich
301ãªãã€ã¬ã¯ããè¡ãããã«ExpressãµãŒããŒã䜿çšããå¿
èŠã¯ãããŸããã ããªãã®èŠä»¶ã«äŸåããŸãããç§ã¯ã«ã¹ã¿ã server.js
ç§ã®ãã®ãæºããããšãã§ããŸããã
ã¹ã©ãã·ã¥ã«ãŒããšéã¹ã©ãã·ã¥ã«ãŒãã§éè€ã³ã³ãã³ãã®ããã«ãã£ãçºçããªãããã301ãªãã€ã¬ã¯ããSEOã«æé©ãªæ¹æ³ã§ãã
ç§ã¯â€ïžNext.jsã倧奜ãã§ããããã®åé¿çãªãã§ãããåŠçããããšã«æ祚ããŸãã
// server.js
const { createServer } = require('http');
const { parse } = require("url");
const next = require("next");
const dev = process.env.NODE_ENV !== 'production'
const port = parseInt(process.env.PORT, 10) || 3000;
const app = next({ dev, quiet: false });
const handle = app.getRequestHandler();
(async () => {
await app.prepare();
const server = createServer();
server.on('request', async (req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
if (pathname.length > 1 && pathname.slice(-1) === "/") {
console.log('server.js - redirect on "/"...', pathname, query);
const queryString = await Object.keys(query).map(key => key + '=' + query[key]).join('&');
res.writeHead(301, { Location: pathname.slice(0, -1) + (queryString ? '?'+ queryString : '') });
res.end();
}
handle(req, res, parsedUrl);
});
await server.listen(port);
console.log(`ð Ready on http://localhost:${port}`);
})();
@Janpot
ããã¯ããªãã®SEOãå·ã€ããŸããã ã°ãŒã°ã«ã¯æ«å°Ÿã®ã¹ã©ãã·ã¥ãå¥ã®ããŒãžãšããŠæ±ããŸãã 404ã䜿çšããŠãããµã€ãå ã®ä»ã®ååšããªãããŒãžãããSEOã«åœ±é¿ãäžããããšã¯ãããŸããã
ç§ã¯ãããSEOãæ¬è³ªçã«ç¹ã«å·ã€ããªããšããããªãã®ãã€ã³ããåããŸãã ãã ããéçºè
ã¯æ¯åURLå®çŸ©ãæ£ããååŸããå¿
èŠãããã人çãšã©ãŒãçºçããå¯èœæ§ããããŸãã NextãåããŠäœ¿çšããéçºè
ã¯ã次ã®ïŒå®å
šã«éåžžã®å€èŠ³ã®ïŒURLã404ããŒãžã«ã€ãªããããšãå¿
ãããç¥ã£ãŠãããšã¯éããŸããã <Link href='/people/'>
æçãããã¬ãŒã ã¯ãŒã¯ã¯ãçæ³çã«ã¯imoã®ãããªãã¥ãŒãã³ãšã©ãŒã®åœ±é¿ãåããŠã¯ãªããŸããã
ãã®äžãæ«å°Ÿã®ã¹ã©ãã·ã¥ã§ãªã³ã¯ããªãéããã°ãŒã°ã«ã¯ãããããããã¯ããŒã«ããããšã¯ããŸããã
åã³-å¶ç¶_ãžã®ãªã³ã¯äººã ã®åé¡ãååšããwww.mysite.com/people/_代ããã«_ã®www.mysite.com/people_ ïŒ -ã§ããã»ãšãã©ã®éçºè ã®ãŠãŒã¶ãŒã«å¯ŸããŠãŸã£ããåãããã«èŠããã©ã¡ããïŒãã
ãããã®ã·ããªãªã¯äž¡æ¹ãšãSEOã«åœ±é¿ãäžããå¯èœæ§ããããŸãã
ããŠãSEOã®åœ±é¿ãèæ
®ããã«ãURLã®æå³çãªæå³ããããŸã-äœã_ããŸãã_ _ www.mysite.com/people / _ã¯äœãæããŸããïŒ çæ³çã«ã¯ããã£ã¬ã¯ããªãæããŠãããããNextã¯pages > people > index.js
ãã®ããã¹ãŠè¿ãå¿
èŠããããŸãïŒ_www.mysite.com/people_ã®pages > people.js
ã¯å¯Ÿç
§çã§ãïŒãã代ããã«äœãè¿ããŸãããããã¯éåžžã«éèŠã§ããã«ãŒãã£ã³ã°ã®åäœã«é«ã¬ãã«ã®æ¬ é¥ããããŸãã
äž»èŠãªã«ãŒãã£ã³ã°ã©ã€ãã©ãªã«ã¯ãããã«å¯Ÿããããã€ãã®ããããžã§ãã³ã°ããã§ã«ãããŸã-ReactRouterã®å Žåã®isExact
ã®ããã«
ç§ã¯ããªããã©ãããæ¥ãŠããã®ãç解ããŠããŸãããããã§ãããã¯æçœãªåé¡ã§ãããã¶ã€ããå¿ èŠããããšæããŸã
next export
å Žåããããã¯å®å
šã«é¿ããããŸããã
誀ã£ãŠãªã³ã¯ãã人ã®åé¡ããããŸã...
ååšããªãURLã«èª€ã£ãŠãªã³ã¯ãããšããåé¡ããããŸããããªã/some/path/
ã¯/some/path/dhgfiuwo
ãããååšããªãã®ã§ããããã
URLã®æå³ããããŸã
ããã¯éåžžã«äž»èŠ³çã§ããç§ãç¥ãéããã»ãã³ãã£ãã¯ã®éããèŠå®ããä»æ§ã¯ãããŸããã URLä»æ§ã«ãããšãæ«å°Ÿã®ã¹ã©ãã·ã¥ãããå Žåãšãªãå Žåã¯ç°ãªãURLãšèŠãªãããŸãã ç§ã¯å°ãªããšã7ã€ã®ç°ãªãæå¹ãªè¡åãèããããšãã§ããŸãïŒ
ãããã /pages/some-page.js
ãš/pages/some-page/index.js
ããããïŒãŸãã¯äž¡æ¹ïŒãæã€å¯èœæ§ãšçµã¿åãããŸãã
next.jsã¯ããããã¹ãŠã®ãŠãŒã¹ã±ãŒã¹ããµããŒãããå¿ èŠããããŸããïŒ ããã©ã«ãã®åäœãéžæããå¿ èŠããããŸããïŒ
ç§ã¯ããã«å察ããŠããŸãããã以åã«ãããå®è£ ããããšããåŸãåœåã®èŠãç®ããããã¥ã¢ã³ã¹ãå€ããšæããŸãã
ååšããªãURLã«èª€ã£ãŠãªã³ã¯ãããšããåé¡ããããŸããããªã/ some / path /ã¯/ some / path / dhgfiuwoãããååšããªãã®ã§ããããïŒ
/some/path/dhgfiuwo
å Žåã人ã
ã¯dhgfiuwo
ã«ãŒããæ¬ èœããŠããå¯èœæ§ããããšäºæ³ããŠããŸãã ïŒããšãã°ããŠãŒã¶ãŒdhgfiuwo
ãã·ã¹ãã ã§èŠã€ããããæ¹æ³users/dhgfiuwo
ãééã£ãŠããŸããã·ã¹ãã ã«ãŠãŒã¶ãŒãããªãããšã¯äºæ³ãããããšã§ããïŒ
ã±ãŒã¹/some/path/
-ããã¯ä»ã®ãµã€ãã®ããã©ã«ãã®åäœã§ããããã人ã
ã¯ãã®ãã¹ã/some/path
ãšåãã§ãããšæåŸ
ããŠããŸãã
ãããã£ãŠã would/some/path/
ã§ã®é害ã¯ã /some/path/dhgfiuwo
ãããååšããŸããã
ä»ã®äººããœãªã¥ãŒã·ã§ã³ãæçš¿ããŠããã®ãèŠãã®ã§ãç§ã®ã¢ãããŒããå ±æããããšæããŸããïŒ https ïŒ
ïŒ=ã«é¢ããŠã¯ãåçã«ã«ãŒãã£ã³ã°ãããããŒãžã®ããã€ãã®æ¹åãšãµããŒããIMOã§è¡ãå¿ èŠããããŸãããããã¯ã¢ã€ãã¢ã瀺ãããã ãã®ãã®ã§ãã
ç°¡åãªè§£æ±ºçãšããŠãããã©ã«ãã®_error
ããŒãžã眮ãæããããšãã§ããŸãïŒ @DevSpeakã®äŸã®ããã«ïŒã
@DevSpeak ãç§ã¯ããªãã®ãªããžããªã«ããã€ãã®å€æŽããå§ãããŸãïŒ
if (typeof window === 'undefined') { ... }
ã©ããããŠãã¯ã©ã€ã¢ã³ããã³ãã«ããããªãŒã·ã§ã€ã¯ã§ããŸããTypescriptãããžã§ã¯ãã§äœ¿çšããŠãããã®ã¯æ¬¡ã®ãšããã§ãïŒçµã¿èŸŒã¿ã®ãšã©ãŒããŒãžã«åºã¥ãïŒïŒ
/pages/_error.tsx
ïŒãŸãã¯TypeScriptã¿ã€ããåé€ããŠ/pages/_error.jsx
ãšããååãä»ããŸãïŒïŒ
import React from 'react';
import Head from 'next/head';
import { NextPageContext } from 'next';
const statusCodes: { [code: number]: string } = {
400: 'Bad Request',
404: 'This page could not be found',
405: 'Method Not Allowed',
500: 'Internal Server Error'
};
export type ErrorProps = {
statusCode: number;
title?: string;
};
/**
* `Error` component used for handling errors.
*/
export default class Error<P = {}> extends React.Component<P & ErrorProps> {
static displayName = 'ErrorPage';
static getInitialProps({
req,
res,
err
}: NextPageContext): Promise<ErrorProps> | ErrorProps {
const statusCode =
res && res.statusCode ? res.statusCode : err ? err.statusCode! : 404;
if (typeof window === 'undefined') {
/**
* Workaround for: https://github.com/zeit/next.js/issues/8913#issuecomment-537632531
* Test vectors:
* `/test/test/` -> `/test/test`
* `/test/////test////` -> `/test/test`
* `/test//test//?a=1&b=2` -> `/test?a=1&b=2`
* `/test///#test` -> `/test#test`
*/
const correctPath = (invalidPath: string) =>
invalidPath
.replace(/\/+$/, '')
.replace(/\/+#/, '#')
.replace(/\/+\?/, '?')
.replace(/\/+/g, '/');
if (req && res && req.url && correctPath(req.url) !== req.url) {
res.writeHead(302, {
Location: correctPath(req.url)
});
res.end();
}
const reqInfo = req
? `; Url: ${req.url}; IP: ${req.headers['x-forwarded-for'] ||
(req.connection && req.connection.remoteAddress)};`
: '';
console.log(`Error rendered: ${statusCode}${reqInfo}`);
}
return { statusCode };
}
render() {
const { statusCode } = this.props;
const title =
this.props.title ||
statusCodes[statusCode] ||
'An unexpected error has occurred';
return (
<div style={styles.error}>
<Head>
<title>
{statusCode}: {title}
</title>
</Head>
<div>
<style dangerouslySetInnerHTML={{ __html: 'body { margin: 0 }' }} />
{statusCode ? <h1 style={styles.h1}>{statusCode}</h1> : null}
<div style={styles.desc}>
<h2 style={styles.h2}>{title}.</h2>
</div>
</div>
</div>
);
}
}
const styles: { [k: string]: React.CSSProperties } = {
error: {
color: '#000',
background: '#fff',
fontFamily:
'-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif',
height: '100vh',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
desc: {
display: 'inline-block',
textAlign: 'left',
lineHeight: '49px',
height: '49px',
verticalAlign: 'middle'
},
h1: {
display: 'inline-block',
borderRight: '1px solid rgba(0, 0, 0,.3)',
margin: 0,
marginRight: '20px',
padding: '10px 23px 10px 0',
fontSize: '24px',
fontWeight: 500,
verticalAlign: 'top'
},
h2: {
fontSize: '14px',
fontWeight: 'normal',
lineHeight: 'inherit',
margin: 0,
padding: 0
}
};
ããã¯ãããŒãžãããããããšãã«ããšã©ãŒããã°ã«èšé²ããããããã°ã確èªããŠãªã³ã¯ããã®ä»ã®åé¡ãä¿®æ£ã§ããããšã«æ³šæããŠãã ããã
@ DevSpeak @ bitjsonãææ¡ããããšãããããŸãã ããã¯ç¢ºãã«ãããå®è¡ãã1ã€ã®æ¹æ³ã§ãããåââé¡ãéåžžã«ããŸã解決ããŸãã ãããã _error.jsx
ã¯å
ã
ãã«ãŒãã£ã³ã°ããžãã¯ãæ ŒçŽããã®ã§ã¯ãªãã_errors_ãåŠçããããšãç®çãšããŠããããšãèãããšããã®ã³ãŒãããã¹ãŠæã£ãŠãããšãããããŒã§éåžžã«å®£èšçã§ãã ãã¹ãŠã®ãŠãŒã¶ãŒããã¹ãŠã®ã³ãŒãããŒã¹ã§ãããè¡ãããšãæåŸ
ããããšã¯å¿
é ã§ã¯ãããŸãã-ããã¯ç®±ããåºããŠæ¥ãã¯ãã§ãã =ãã®æ¡ä»¶ã¯ãReact Routerã®ããã«ãªããã¢ãŠããããªãã·ã§ã³ã䜿çšããŠãã«ãŒãã£ã³ã°ããžãã¯ã«çµã¿èŸŒãå¿
èŠããããšæããŸãã
@NathanielHill
ããã¯æ¬¡ã®èŒžåºã®å Žåã«ãå®å šã«é¿ããããŸãã
åŸ ã£ãŠãã ãã-ããã¥ã¡ã³ããèªãã§ãæ«å°Ÿã®ã¹ã©ãã·ã¥æ¡ä»¶ãåŠçããããã®ç¹å®ã®ã³ãŒããããããšãç解ããŸããã
ããŒãžã¯htmlãã¡ã€ã«ãšããŠãšã¯ã¹ããŒããããŸããã€ãŸãã/ aboutã¯/about.htmlã«ãªããŸãã
ããŒãžãindex.htmlãã¡ã€ã«ãšããŠãšã¯ã¹ããŒãããããã«Next.jsãæ§æããæ«å°Ÿã«ã¹ã©ãã·ã¥ãä»ããããšãã§ããŸããã€ãŸãã/ aboutã¯/about/index.htmlã«ãªãã/ about /ãä»ããŠã«ãŒãã£ã³ã°ã§ããŸãã ããã¯ãNext.js 9ããåã®ããã©ã«ãã®åäœã§ããã次ã®next.config.jsã䜿çšããŠããã®åäœã«æ»ãããšãã§ããŸãã
// next.config.js
module.exports = {
exportTrailingSlash: true,
}
ãããnext export
ãä»ããéçHTMLãšã¯ã¹ããŒãã®ãªãã·ã§ã³ã§ã¯ãªãå Žåã§ããNextããã®ïŒé©ãã¹ãïŒæ©èœããµããŒãããŠãããšããçç±ã ãã§ãä»ã®ã¢ãŒããèŠããå¿
èŠããããšããè«çã«ã¯åæããŸããïŒç§ã¯ããããŸããïŒäœ¿çšçµ±èšãç¥ã£ãŠããŸãããç¹ã«ããããã®ãããªäžè¬çãªãŠãŒã¹ã±ãŒã¹ã§ããããšãããã£ãŠããå Žåã¯ããµãŒããŒã¬ã¹ã§ã¯ãªãéåžžã®ãµãŒããŒä»ãã¢ãŒãã䜿çšãã人ãå€ããšæããŸãïŒ
åèïŒèå³ããããããããªãRFCããããŸãhttps://github.com/zeit/next.js/issues/9081
// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/:path*/",
destination: "/:path",
statusCode: 301
}
];
}
};
@Janpot Love next.config.js
ãªãã€ã¬ã¯ããèšå®ããå¿
èŠããããããããã¯äŸç¶ãšããŠäžå¯æ¬ ã§ãããŸãã¯ãæ£èŠè¡šçŸã䜿çšããŠã @ bitjsonã®ãããªãã¹ãŠã®ã±ãŒã¹ããã£ããããããšã
.replace(/\/+$/, '')
.replace(/\/+#/, '#')
.replace(/\/+\?/, '?')
.replace(/\/+/g, '/')
ãããã®å Žåããã³ã¢ããŒã ããã®RFCãåªå ããŠããå Žåã¯ãããã«äžæ©é²ãã§ãçµã¿èŸŒã¿ã®_config_ã«ããŠããã®ããã«
// next.config.js
module.exports = {
ignoreStrictRoutes: false, // default value: true
};
å šäœãšããŠãããã¯å€§ããªåé²ã ãšæããŸã-è¯ããã®@Timer !! ð¥
@ nik-john "/:path*/"
æå®ãããã¹ã¯ãã¹ãŠããã£ããããå¿
èŠããããŸãïŒ :path
ã¯åäžã®ã»ã°ã¡ã³ãããã£ãããã *
ã¯0ããnåã®ã€ã³ã¹ã¿ã³ã¹ããã£ããããŸãïŒã
@Janpotããç§ã®æªãð€Šââç§ãã¡ã¯ãŸãããã®æ£èŠè¡šçŸã®æ«å°Ÿã®ã¯ãšãªãã©ã¡ãŒã¿ãèæ ®ããå¿ èŠããããšæããŸã
ãŸããç§ã¯ãŸã 2çªç®ã®éšåãæ¯æããŠããŸãïŒ
ãããã®å Žåããã³ã¢ããŒã ããã®RFCãåªå ããŠããå Žåã¯ãããã«äžæ©é²ãã§ããªããã¢ãŠãã§ããçµã¿èŸŒã¿ã®æ§æã«ããããšã匷ããå§ãããŸãã
// next.config.js
module.exports = {
ignoreStrictRoutes: false, // default value: true
};
ã«ã¹ã¿ã ãµãŒããŒã䜿çšããŠããŠãå³å¯ãªã«ãŒããç¡èŠãããå Žåã¯ããªãã€ã¬ã¯ããè¡ã代ããã«ã«ã¹ã¿ã ã«ãŒããã³ãã©ãŒã䜿çšããããšãã§ããŸãã
app.render(req, res, urlWithoutTrailingSlash, query);
ãã®ããã«ããŠã /path
ãš/path/
äž¡æ¹ããµããŒãããåãããŒãžã«è§£æ±ºããããšãã§ããŸãã
Oauthãã§ãã¬ãŒã·ã§ã³ãããã€ããŒã¯ãå€ãã®å Žåãæ«å°Ÿã®ã¹ã©ãã·ã¥ãå¿ èŠãšããããããã®åäœã«ãããåçŽãªãããŒãéåžžã«è€éã«ãªããŸãã ãã®åäœãå®è£ ããéã®æè¡çãªèª²é¡ã¯äœã§ããïŒ ãããšããããã¯æ¬¡ããã®èšèšäžã®æ±ºå®ã§ããïŒ
ãã®ã¹ã¬ããã§ã¯ãããŸã§èšåãããŠããŸããããNowã§ãããã€ããåŸã¯ãã®åé¡ã¯çºçããŠããŸããã now dev
ãã¹ãããå Žåã«ã®ã¿ããŒã«ã«ã§çºçããŠããŸãã
const removeTrailingSlashes = (req, res, expressNext) => {
if (req.path.substr(-1) === '/' && req.path.length > 1) {
const query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
expressNext();
}
};
ãããstackoverflowããååŸããå®å šã«æ©èœããŸããã ãã®ãœãªã¥ãŒã·ã§ã³ã¯Expressã§æ©èœããŸãã
@GaneshKathar Expressã䜿çšããŠããªã
ããã«ã€ããŠã¯åæã§ããªããšæããŸããæ§æå¯èœã§ããå¿ èŠããããŸãã
ç§ã¯å®éã«ã¯åžžã«æ«å°Ÿã®ã¹ã©ãã·ã¥ãå¿ èŠã§ãããã¹ãŠã®ããŒãžãæ«å°Ÿã®ã¹ã©ãã·ã¥ã§çµããå Žåãçžå¯ŸURLãæšè«ããæ¹ãç°¡åã§ãã
äŸãã°ããã¯ããšããæå³ã«ãªããŸãã/about/index.tsx
ã§/about
ã®ä»£ããã«/about/
ã¹ã©ãã·ã¥ãä»ããã«ãä»ãã®æ¬¡ãæåŸ
ããããç解ã§ããŸãã ãã¹ãŠã®ããŒãžãã¹ã©ãã·ã¥ã§çµããå Žåãå°æ¥çã«ããŒãžã«ãµãããŒãžãå«ããããšãã§ããããã«ãªããŸããããã¯ãããŒãžã«ãšã£ãŠããæ¡åŒµå¯èœãªæ¹æ³ã ãšæããŸãã
/about/index.tsx
ãã¡ã€ã«å
ã«çžå¯Ÿãªã³ã¯ãäœæããã®ã¯é¢åã§ãã ãªã³ã¯ã./mysubpage/
ãããšã代ããã«ãµã€ãã®ã«ãŒããæããŸãã ããã«ããããµãããŒãžã®ååãå€æŽã§ããªããªããŸãã çžå¯Ÿãªã³ã¯ãç·šéããå¿
èŠããããããååãå€æŽã§ããããŒãžã§ãã£ã¬ã¯ããª/about/
äœæããããšã¯ã§ããŸããã
ãŸãã wget -r
ãµã€ãã¯ãåžžã«æ«å°Ÿã«ã¹ã©ãã·ã¥ããããindex.htmlãã¡ã€ã«ãçæãããšããè³¢æãªçµæãçæããŸãã
ãã ãããã®èšå®ãå€æŽãããšããã¹ãŠã®ãµã€ãã§æ«å°Ÿä»¥å€ã®ã¹ã©ãã·ã¥ãå¿ èŠã«ãªããããå€æŽãå€§å¹ ã«äžæãããŸãããã®ãããæ§æå¯èœã§ããå¿ èŠããããŸãã
ããŒãžã§ã³9ã䜿çšããŠããŸããããã®åé¡ã¯ãŸã 解決ãããŠããŸãã
next.config.js
次ã®ãããªãã®ã䜿çšããŠããããæ©èœãããããšãã§ããŸããã
exportPathMap: async function() {
const paths = {
'/': { page: '/' },
'/authors/index.html': { page: '/authors' },
};
return paths;
},
/authors
ã¢ã¯ã»ã¹ãããšã302ãlocation
ã/authors/
ãŸãã http-serve
ã§ãã¹ãããŠããŸããããã®åäœããµãŒããŒåºæã§ãããã©ããã¯ããããŸããã
ãã®åé¡ã«çŽé¢ãããšããç§ã¯ãã®è§£æ±ºçãæãã€ããŸãã
ç§ã®_error.js
ããŒãžã§
Error.getInitialProps = ({ res, err, asPath }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
const checkForTrailingSlashes = () => {
if (asPath.match(/\/$/)) { // check if the path ends with trailing slash
const withoutTrailingSlash = asPath.substr(0, asPath.length - 1);
if (res) {
res.writeHead(302, {
Location: withoutTrailingSlash
})
res.end()
} else {
Router.push(withoutTrailingSlash)
}
}
}
if (statusCode && statusCode === 404) {
checkForTrailingSlashes();
} else {
//
}
return { statusCode };
}
ããã¯åé¡ãå æããããã®è¯ãæ¹æ³ã§ããïŒ
ããã¯ã©ãïŒ
pages / _app.jsx
`` `import React from'react ';
'next / app'ããã¢ããªãã€ã³ããŒãããŸãã
ããã©ã«ãã¯ã©ã¹ã®ãšã¯ã¹ããŒãMyAppã¯Appãæ¡åŒµããŸã{
äžããïŒïŒ {
const {ComponentãpagePropsãrouterïŒ{asPath}} = this.props;
// Next.js currently does not allow trailing slash in a route.
// This is a client side redirect in case trailing slash occurs.
if (asPath.length > 1 && asPath.endsWith('/')) {
const urlWithoutEndingSlash = asPath.replace(/\/*$/gim, '');
if (typeof window !== 'undefined') {
window.location.replace(urlWithoutEndingSlash);
}
return null;
}
return <Component {...pageProps} />;
}
}
`` `
@cnblackxpææ¡ãããããšãã ããã¯ç§ãå©ããŸããã éãã¬ãŒãªã³ã°404ã®ããã©ã«ãã®åäœãç¶æããããã«ããããå®è£
ããæ¹æ³ã¯æ¬¡ã®ãšããã§ãïŒã€ãŸããããã©ã«ãã®Error
å®è£
ãåã«åãšã¯ã¹ããŒãããŠããŸãïŒã
import Error from "next/error";
import Router from "next/router";
export default Error;
Error.getInitialProps = ({ res, err, asPath }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
if (statusCode && statusCode === 404) {
if (asPath.match(/\/$/)) {
const withoutTrailingSlash = asPath.substr(0, asPath.length - 1);
if (res) {
res.writeHead(302, {
Location: withoutTrailingSlash
});
res.end();
} else {
Router.push(withoutTrailingSlash);
}
}
}
return { statusCode };
};
ãããä»ã«äœã決ãŸã£ãŠããªãéãã
@AlexSapoznikovã®åé¿çã®å°ããªæ¹åïŒ
render() {
const { Component, pageProps, router: { asPath } } = this.props;
// Next.js currently does not allow trailing slash in a route.
// This is a client side redirect in case trailing slash occurs.
if (pageProps.statusCode === 404 && asPath.length > 1 && asPath.endsWith('/')) {
ããã§ã®å¯äžã®éãã¯ãã¹ããŒã¿ã¹ã³ãŒãã404ã§ããããšã確èªããããšã§ãããªãã€ã¬ã¯ãã®ããã«ãµãŒããŒäžã§åžžã«ã¬ã³ããªã³ã°ãããŠããåçã«ãŒãã«Linkã䜿çšãããšåé¡ãçºçããŸããã ã¯ã©ã€ã¢ã³ãåŽã®ã«ãŒãã£ã³ã°ãæ©èœããããå Žåã¯ããªã³ã¯href
ããããã«æ«å°Ÿã®ã¹ã©ãã·ã¥ãè¿œå ããããšã¯ã§ããŸãããããã®å Žåã¯ãªãã€ã¬ã¯ãããªãããã«ããå¿
èŠããããŸãã
ãšã©ãŒã³ã³ããŒãã³ãã§åé¿çãå®è£ ããéã®åé¡ã¯ãéçºäžã«éç¥ãšã©ãŒãã¹ããŒãããæ°ã«ãªãããšã§ãã 以åã®ã¯ã©ã€ã¢ã³ãåŽãªãã€ã¬ã¯ãã«å¯Ÿããããã€ãã®æ¹åïŒ
æ¹åãããã®ã¯ãã¯ã©ã€ã¢ã³ãåŽã§next / routerã䜿çšããURLã®çœ®æããªããŒããªãã§è¡ãããããã«ãªã£ãããšã§ãã
pages / _app.jsx
import App from 'next/app';
import Router from 'next/router';
export default class MyApp extends App {
render() {
const { Component, pageProps, router: { asPath, route } } = this.props;
// Next.js currently does not allow trailing slash in a route.
// This is a client side redirect in case trailing slash occurs.
if (pageProps.statusCode === 404 && asPath.length > 1 && asPath.endsWith('/')) {
const routeWithoutEndingSlash = route.replace(/\/*$/gim, '');
const asPathWithoutEndingSlash = asPath.replace(/\/*$/gim, '');
if (typeof window !== 'undefined') {
Router.replace(routeWithoutEndingSlash, asPathWithoutEndingSlash);
}
return null;
}
return <Component {...pageProps} />;
}
}
404ä¿®æ£ã®ããã®@mbrowneã«ãæè¬ããŸã:)
@cansinã®ãœãªã¥ãŒã·ã§ã³ãæ¡çšããã¯ãšãªãã©ã¡ãŒã¿ãåŠçããæ©èœãè¿œå ããŸãã
MyError.getInitialProps = async ({ res, err, asPath }) => {
// Capture 404 of pages with traling slash and redirect them
const statusCode = res
? res.statusCode
: (err ? err.statusCode : 404);
if (statusCode && statusCode === 404) {
const [path, query = ''] = asPath.split('?');
if (path.match(/\/$/)) {
const withoutTrailingSlash = path.substr(0, path.length - 1);
if (res) {
res.writeHead(302, {
Location: `${withoutTrailingSlash}${query ? `?${query}` : ''}`,
});
res.end();
} else {
Router.push(`${withoutTrailingSlash}${query ? `?${query}` : ''}`);
}
}
}
@pinpointcoderã¯ãæ«å°Ÿã«ã¹ã©ãã·ã¥ããããã¯ãšãªãã©ã¡ãŒã¿ãåæã«çºçããURLã®äŸãæäŸã§ããŸããïŒ ããªãã¯/blog/?123
ã®ç·ã«æ²¿ã£ãŠèããŠããŸããïŒ
äžèšã®åé¿çã®ããã€ããã¿ããªã«æè¬ããŸãã 圌ããåããŠããŸããïŒ
ãã ããNextã®ããŒã ãããã®åé¡ãä¿®æ£ããå ¬åŒã®æ¹æ³ã¯ãããŸããïŒ ãã®åé¡ã¯äœå¹ŽãåãããããŸãã
次ã®ãšã¯ã¹ããŒãã§ã¯ããã£ã¬ã¯ããªããŒãžã«æ«å°Ÿã®ã¹ã©ãã·ã¥ã衚瀺ãããŸãã
@pinpointcoderã¯ãæ«å°Ÿã«ã¹ã©ãã·ã¥ããããã¯ãšãªãã©ã¡ãŒã¿ãåæã«çºçããURLã®äŸãæäŸã§ããŸããïŒ ããªãã¯
/blog/?123
ã®ç·ã«æ²¿ã£ãŠèããŠããŸããïŒ
@coodoo圌ã§ã¯ãããŸããããã¯ããæ®å¿µãªããããã¯
æ£èŠURLã«çŸåšæ«å°Ÿã®ã¹ã©ãã·ã¥ãå«ãŸããŠãã倧éã®ããã°æçš¿ã移è¡ããããšããŠããã®ã§ãããã¯ä»ã®ç§ã®ãå°»ã®å€§ããªçã¿ã§ãã
ãããåŠçããããã«ã«ã¹ã¿ã ãµãŒããŒãå®è£ ããããšã«ããŸããããããã¯ç°¡åã§ãnext.jsã®ãã¡ã€ã«ããŒã¹ã®ã«ãŒãã£ã³ã°ã·ã¹ãã ãåŒãç¶ã䜿çšã§ããŸãã ããããã°ãnext.jsãèªèããURLãæžãæããŠããå®éã®URLã®æ«å°Ÿã«ã¯ã¹ã©ãã·ã¥ãä»ãããŸãŸã«ãªããŸãã
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const conf = require('./next.config.js')
const PORT = process.env.PORT || 5000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev, conf })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// If there is a slash at the end of the URL, remove it before sending it to the handle() function.
// This is a workaround for https://github.com/zeit/next.js/issues/5214
const url =
req.url !== '/' && req.url.endsWith('/')
? req.url.slice(0, -1)
: req.url
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(url, true)
handle(req, res, parsedUrl)
}).listen(PORT, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${PORT}`)
})
})
https://nextjs.org/docs/advanced-features/custom-serverãåç §ããŠ
@mbrowneå®éã«ã¯ãã«ã¹ã¿ã ãµãŒããŒã䜿çšããçç±ã¯ãããããããŸããããããŸã§å®è£ ã§ããªãã£ãäž»ãªçç±ã¯ãèªåéçæé©åã倱ãããããšã§ãã éçã«ãŒããæåã§æå®ã§ãããã©ããç¥ã£ãŠããŸããïŒ
çŸæç¹ã§ã¯ãã¢ããªã®èªåéçæé©åã¯å¿ èŠãªãããã調æ»ããŠããŸããã
ç§ãã«ã¹ã¿ã ãµãŒããŒã䜿çšããŠããŸãããå€æŽãããïŒã¹ã©ãã·ã¥ãªãã§ïŒURLãhandle
ã«æž¡ããšãSSRã¯ã¯ã©ã€ã¢ã³ãåŽãšã¯ç°ãªãURLãèªèããŸãã
ç§ã¯next
ã«ãŒã¿ãŒãããããã®åä»ãªãããã³ã°ãªãã«URLãå
é ã®ã¹ã©ãã·ã¥ãšäžèŽãããããšã奜ã¿ãŸãã
2020幎ãšãã®ãã°ã¯ãŸã çºçããŠããŸãã ä¿¡ããããªã
ããã¯æªããã°ã§ãããæ¬åœã«ä¿®æ£ããå¿
èŠããããŸãã /products
æ©èœããŸããã /products/
ã¯æ©èœããŸããã ãã®ãªã³ã¯ã§
<Link href="/products">
<a>Products</a>
</Link>
ç§ã¯åŸã
index.js:1 Warning: Prop `href` did not match. Server: "/products" Client: "/products/"
ãã ãããªã³ã¯ã/products/
ã«ãã€ã³ããããªã³ã¯ã«ã¢ã¯ã»ã¹ããŠãéçºäžã«ããŒãžãæŽæ°ãããšã404ã衚瀺ãããŸããããã¯éåžžã«èŠçãªéçºçµéšã§ãã
ãã®åé¡ã¯1ã5幎åã«æåã«å ±åãããŸããã å ¬åŒã®ä¿®æ£ããé¡ãã§ããŸããïŒ ããã¯9.3.4ã«ãŸã ååšããŠããŸãã
SEOã®çç±ãããã³ã³ãã³ãã衚瀺ãã代ããã«ãæ«å°Ÿä»¥å€ã®ã¹ã©ãã·ã¥URLã«ãªãã€ã¬ã¯ãããŸããã
app.prepare().then(() => {
createServer((req, res) => {
if (req.url !== '/' && req.url.endsWith('/')) {
res.writeHead(301, { Location: req.url.slice(0, -1) })
res.end()
}
handle(req, res, parse(req.url, true))
}).listen(PORT, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${PORT}`)
})
})
SEOã®å Žåã rel="canonical"
ã圹ç«ã€å ŽåããããŸãããããã§ããã®404ã®åé¡ãä¿®æ£ããå¿
èŠããããŸãã
ããã¯æªããã°ã§ãããæ¬åœã«ä¿®æ£ããå¿ èŠããããŸãã
/products
æ©èœããŸããã/products/
ã¯æ©èœããŸããã ãã®ãªã³ã¯ã§<Link href="/products"> <a>Products</a> </Link>
ç§ã¯åŸã
index.js:1 Warning: Prop `href` did not match. Server: "/products" Client: "/products/"
ãã ãããªã³ã¯ã
/products/
ã«ãã€ã³ããããªã³ã¯ã«ã¢ã¯ã»ã¹ããŠãéçºäžã«ããŒãžãæŽæ°ãããšã404ã衚瀺ãããŸããããã¯éåžžã«èŠçãªéçºçµéšã§ãããã®åé¡ã¯1ã5幎åã«æåã«å ±åãããŸããã å ¬åŒã®ä¿®æ£ããé¡ãã§ããŸããïŒ ããã¯9.3.4ã«ãŸã ååšããŠããŸãã
ç§ãçŸåšãã®åé¡ãæ±ããŠããŸãã
ãããç§ããããä¿®æ£ããæ¹æ³ã§ãã httpsïŒ //medium.com/@thisisayush/handling -404-trailing-slash-error-in-nextjs-f8844545afe3
ãããç§ããããä¿®æ£ããæ¹æ³ã§ãã httpsïŒ //medium.com/@thisisayush/handling -404-trailing-slash-error-in-nextjs-f8844545afe3
ããŒã«ã«ã§éçºããå Žåãããã«ã¯ã«ã¹ã¿ã ãµãŒããŒãå¿ èŠã§ãããå¿ é ã§ã¯ãããŸããããããããšãããããŸãã
@timneutkensãã®åé¡ã®ä¿®æ£ãéçºã¹ã±ãžã¥ãŒã«ã«çµã¿èŸŒãããšãã§ããå¯èœæ§ã¯ãããŸããïŒ
ããã«éèŠãªããšã«ããªãã€ã¬ã¯ããœãªã¥ãŒã·ã§ã³ã¯ãæ¬çªç°å¢ã§ã¹ã©ãã·ã¥ãåé€ããã®ã§ã¯ãªããã¹ã©ãã·ã¥ãè¿œå ããããã«æ¢ã«èšå®ãããŠãããµã€ããç¶æããŠãããŠãŒã¶ãŒã«ã¯æ©èœããŸããã ãã¬ãŒã ã¯ãŒã¯ããã®éžæãæ£æçã«æ瀺ããã¹ãã§ã¯ãªããšæããŸãã
@AlexSapoznikovã®ãœãªã¥ãŒã·ã§ã³ã¯ãNetlifyã§ããŸãæ©èœããŸããïŒããã©ã«ãã§æ«å°Ÿã«ã¹ã©ãã·ã¥ãè¿œå ãããŸãïŒã ã¯ãšãªãã©ã¡ãŒã¿ã®ãµããŒããè¿œå ããé«åºŠãªããŒãžã§ã³ã¯æ¬¡ã®ãšããã§ãã
import App from "next/app";
export default class MyApp extends App {
render() {
const { Component, pageProps, router, router: { asPath } } = this.props;
// Next.js currently does not allow trailing slash in a route, but Netlify appends trailing slashes. This is a
// client side redirect in case trailing slash occurs. See https://github.com/zeit/next.js/issues/5214 for details
if (asPath && asPath.length > 1) {
const [path, query = ""] = asPath.split("?");
if (path.endsWith("/")) {
const asPathWithoutTrailingSlash = path.replace(/\/*$/gim, "") + (query ? `?${query}` : "");
if (typeof window !== "undefined") {
router.replace(asPathWithoutTrailingSlash, undefined, { shallow: true });
return null;
}
}
}
return <Component {...pageProps} />;
}
}
ä»ã®SDKããã©ãããã©ãŒã ã§ã®ãœãããŠã§ã¢éçºã®çµéšã¯ãããŸãããç§ã¯Next JSã®åå¿è ãªã®ã§ããè©«ã³ç³ãäžããŸãã
ãã®ããã°ãã¯ç§ãæãé©ããããšæããŸãã ç§ã«ãšã£ãŠãããã¯ãé©ãæå°ã®ååãã«éåããŠããŸããã index.tsxã/ pages / about /ãã©ã«ããŒã«é 眮ããã®ã§ã/ about /ãš/ aboutãåãããã«æ©èœããããšãæåŸ ããŠããŸããã
ç§ã¯1990幎代åŸåã«ãµãŒããŒã«HTMLFTPã䜿çšããŠWebãµã€ããäœæãå§ãããã®åŸPHPãšApacheããããŠæçµçã«ã¯JavaãµãŒããŒã«ç§»è¡ããŸããã ä»ã¯ã¢ãã€ã«ã¢ããªãå°éãšããŠããŸãã ãã®åäœãããã©ã«ãã§ã¯ãªããéçºãµãŒããŒã§ä¿®æ£ããããã«ã«ã¹ã¿ã ãµãŒããŒããŒãžãäœæããå¿ èŠãããã®ã¯ãç§ã«ã¯å¥åŠã«æããŸãã
éçãšã¯ã¹ããŒããå®è¡ããäºå®ãªã®ã§ãã«ã¹ã¿ã ãµãŒããŒãäœæããªããŠããæ¬çªç°å¢ã«ã¯è¡šç€ºãããŸããã ãã ããéçºãšãããã°ã¯å°ãé¢åã«ãªããŸãã
ãããä¿®æ£ããã次ã®éçºããã©ã°ãååŸããŠãæ æ°ãªéçºè ãéçº/ãããã°æéã®ããã ãã«è¿œå ã®ã«ãŒãã£ã³ã°ããžãã¯ãäœæããå¿ èŠããªãããã«ããããšã¯ã§ããŸããïŒ
ããããšãïŒ
psïŒã¯ãã /about
ãš/about/
ãå®å
šã«ç°ãªãURLã§ããããšãç§ã¯ç¥ã£ãŠããŸãã index.tsx
ãã¡ã€ã«ã/pages/about/
ãã©ã«ããŒå
ã«çœ®ããšãæ¬åœã«æ··ä¹±ããŸããããã®ãã¡ã€ã«ã¯/about
ãã¹ã§ã®ã¿æ©èœãã /about/
ã§ã¯æ©èœããªãããšã/about/
ã ãããéã ã£ããšããŠããç§ã¯ããã»ã©é©ããªãã§ãããã
ppsïŒ /about/
ãæã<Link></Link>
ã³ã³ããŒãã³ãããããæåŸ
ã©ããã«æ©èœããå Žåã¯ãããã«æ··ä¹±ããŸããã 次ã«ããã©ãŠã¶ã§æŽæ°ãæŒããšãURLãå€æŽãããŠããªããŠããããã«404ç§ã«ãªããŸãã ããã¯éåžžã«é©ãã¹ãããšã§ããã ïŒ-NS
ããããåŸ
ã£ãŠãã ãããããã¯æªåããŸãïŒ _error.js
å
ã«ã«ã¹ã¿ã checkForTrailingSlash
é¢æ°ãè¿œå ããŸãããããã«ãããæ«å°Ÿã®ã¹ã©ãã·ã¥ãåé€ããããªãã€ã¬ã¯ããããŸãã ããã¯ãïŒæçµçã«ïŒã«ã¹ã¿ã 404ããŒãžãè¿œå ããã«ã¹ã¿ã 404ããŒãžã䜿çšãããšãNext.jsãError
å®å
šã«ãã€ãã¹ããããšãããããŸã§ãã°ããã®éåé¡ãªãæ©èœããŸããã ããã¯ã Error.getInitialProps
å
ã®ã«ã¹ã¿ã ããžãã¯ãæ©èœããªããªãããšãæå³ããŸã-æ«å°Ÿã®ã¹ã©ãã·ã¥ã®ãã§ãã¯ãå«ã¿ãŸãã
ã«ã¹ã¿ã ãµãŒããŒã¯ãŸã å¯èœæ§ããªãã®ã§ãä»ã®äººãèšåãã_app.js
ãœãªã¥ãŒã·ã§ã³ãè©ŠããŠã¿ãããšæããŸãã
@AlexSapoznikovã®ãœãªã¥ãŒã·ã§ã³ã¯ãNetlifyã§ããŸãæ©èœããŸããïŒããã©ã«ãã§æ«å°Ÿã«ã¹ã©ãã·ã¥ãè¿œå ãããŸãïŒã ã¯ãšãªãã©ã¡ãŒã¿ã®ãµããŒããè¿œå ããé«åºŠãªããŒãžã§ã³ã¯æ¬¡ã®ãšããã§ãã
import App from "next/app"; export default class MyApp extends App { render() { const { Component, pageProps, router, router: { asPath } } = this.props; // Next.js currently does not allow trailing slash in a route, but Netlify appends trailing slashes. This is a // client side redirect in case trailing slash occurs. See https://github.com/zeit/next.js/issues/5214 for details if (asPath && asPath.length > 1) { const [path, query = ""] = asPath.split("?"); if (path.endsWith("/")) { const asPathWithoutTrailingSlash = path.replace(/\/*$/gim, "") + (query ? `?${query}` : ""); if (typeof window !== "undefined") { router.replace(asPathWithoutTrailingSlash, undefined, { shallow: true }); return null; } } } return <Component {...pageProps} />; } }
ã³ãŒããµã³ãã«ã«é倧ãªãšã©ãŒããããŸããã¯ãšãªæååã ããasPath
ãšããŠNext.jsã«æž¡ãããšãããããã¯ãšãªãã©ã¡ãŒã¿ã䜿çšããŠã€ã³ããã¯ã¹ã«ãŒãã«ãªã¯ãšã¹ããããšãšã©ãŒãã¹ããŒãããŸãã
ããã¯ãããä¿®æ£ããŸãïŒ
if (asPath && asPath.length > 1) {
const [path, query = ''] = asPath.split('?');
if (path.endsWith('/') && path.length > 1) {
const asPathWithoutTrailingSlash =
path.replace(/\/*$/gim, '') + (query ? `?${query}` : '');
if (typeof window !== 'undefined') {
router.replace(asPathWithoutTrailingSlash, undefined, {
shallow: true,
});
return null;
}
}
}
ãããSSRã§æ©èœãããã«ã¯ã @ pjawsããã³@AlexSapoznikovãœãªã¥ãŒã·ã§ã³ã«ä»¥äžãè¿œå ããå¿ èŠããããŸããã
static async getInitialProps({ Component, ctx, router }) {
/* Fixes the trailing-slash-404 bug for server-side rendering. */
const { asPath } = router;
if (asPath && asPath.length > 1) {
const [path, query = ""] = asPath.split("?");
if (path.endsWith("/") && path.length > 1) {
const asPathWithoutTrailingSlash =
path.replace(/\/*$/gim, "") + (query ? `?${query}` : "");
if (ctx.res) {
ctx.res.writeHead(301, {
Location: asPathWithoutTrailingSlash,
});
ctx.res.end();
}
}
}
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {},
};
}
ããããããã®æ©èœãSSRãšCSRã®äž¡æ¹ã§æ©èœããé¢æ°ã«äžè¬åããäž¡æ¹ã®å ŽæïŒ getInitialProps
ãšrender
ïŒã§åŒã³åºãããšããå§ãããŸãã
ã«
ããã¯ä¿®æ£ãããŸãããã¿ã€ãã«ãééã£ãŠããŸãã ããŒã
@AlexSapoznikov @pjaws
ããªãã®è§£æ±ºçã¯ç§ãã¡ãç¡éã«ãŒãã«é¥ãããŸãïŒ
if (asPath && asPath.length > 1) {
const [path, query = ''] = asPath.split('?');
if (path.endsWith('/') && path.length > 1) {
const asPathWithoutTrailingSlash =
path.replace(/\/*$/gim, '') + (query ? `?${query}` : '');
if (typeof window !== 'undefined') {
router.replace(asPathWithoutTrailingSlash, undefined, {
shallow: true,
});
return null;
}
}
}
å¶åŸ¡ã§ããªãçç±ã«ããã next.config.js
exportTrailingSlash
ãªãã·ã§ã³ã䜿çšããå¿
èŠããããŸãã
å¥ã®ããŒãžãžã®ãªã³ã¯ãå¿
èŠã§ããããªã³ã¯ã/somepage?param=whatever
ãŸãã
次ã®ãªã³ã¯ãããã/somepage/?param=whatever
å€æããããŒãžãèŠã€ãããªãããã§ãã
äžèšã®ãœãªã¥ãŒã·ã§ã³ã䜿çšããŠparamsã®åé¡ã解決ããŸããã /somepage/
ãããªãããã€ãããããŒãžã«ç§»åãããšãç¡éã«ãŒãã«å
¥ããŸãã
@ronyehã¯ããã§æ¬åœã«è¯ãç¹ã
ãããSSRã§æ©èœãããã«ã¯ã @ pjawsããã³@AlexSapoznikovãœãªã¥ãŒã·ã§ã³ã«ä»¥äžãè¿œå ããå¿ èŠããããŸããã
static async getInitialProps({ Component, ctx, router }) { /* Fixes the trailing-slash-404 bug for server-side rendering. */ const { asPath } = router; if (asPath && asPath.length > 1) { const [path, query = ""] = asPath.split("?"); if (path.endsWith("/") && path.length > 1) { const asPathWithoutTrailingSlash = path.replace(/\/*$/gim, "") + (query ? `?${query}` : ""); if (ctx.res) { ctx.res.writeHead(301, { Location: asPathWithoutTrailingSlash, }); ctx.res.end(); } } } return { pageProps: Component.getInitialProps ? await Component.getInitialProps(ctx) : {}, }; }
ããããããã®æ©èœãSSRãšCSRã®äž¡æ¹ã§æ©èœããé¢æ°ã«äžè¬åããäž¡æ¹ã®å ŽæïŒ
getInitialProps
ãšrender
ïŒã§åŒã³åºãããšããå§ãããŸãã
ããã¯getServerSidePropsã®ããããŒãžã§æ©èœããæ«å°Ÿã«ã¹ã©ãã·ã¥ãä»ããURLã¯404ãªãã§åãããŒãžãè¿ãããã«ãªããŸããã
ããããã°ãªããã1ã€ãããŸããåçã«ãŒããšgetStaticPathsã䜿çšããããŒãžãã»ãšãã©ãªããããgetServerSidePropsã䜿çšã§ããªãããããããã®åçã«ãŒããæ«å°Ÿã®ã¹ã©ãã·ã¥ã§åç
§ãããšãæåã«404ãè¿ããã次ã«ããŒãžã«ãªãã€ã¬ã¯ããããŸãã ã
/ api / testãã©ã«ããŒã䜿çšããŠããŸã
ããã¯ã®ããã«åã
ãããŠç§ã¯ãããæ©èœããªãããšãçºèŠããŸãã
ãããé¢é£ããåé¡ãã©ããããããªã
P / D exportTrailingSlash = trueã¯ããã解決ããŸãã
ããã¯éåžžã«å€ãåé¡ã§ãããé·ãé察åŠãããŠããªãçç±ã¯ãããŸããïŒ
äœãæ©èœããŠããªãã®ãããããŸããã
ç§ã®æ§ãããªè¡šçŸã¯ãèŠä»¶ã¯æ¬¡ã®ãšããã§ãã
| | exportTrailingSlashïŒfalse | exportTrailingSlashïŒtrue |
| -------------------------- | ----------------------- ----- | --------------------------- |
| URLã¯/ã§çµãããŸã| åäœããªãã¯ãã§ã| åäœããã¯ãã§ã|
| URLã¯/ã§çµãããªã| åäœããã¯ãã§ã| åäœããªãã¯ãã§ã|
ããã¯æåŸ ã©ããã«æ©èœããŸãã
exportTrailingSlash: false
ã䜿çšããŸãexportTrailingSlash: true
ã䜿çšããnginxã¯url/
ãurl/index.html
å€æããŸã@ andrescabana86ã§ç¢ºèªã§ããããšãããããã¯æ©èœããªãã¯ãã®å Žæã§æ©èœããŸãã GET /api/test/123/
ã«å¯ŸããŠã GET /api/test/
ã¯æ©èœãããæ©èœããªãã¯ãã§ãã
@Izhakiç§ã¯äž¡æ¹ãè©Šããprodã«ãããã€ããŸãã...ãããŠç§ã«ãšã£ãŠã¯æ©èœããŠããŸãã
ãããŠç§ã¯exportTrailingSlash: true
å¿ èŠã«å¿ããŠå ¬éãªããžããªãäœæããŠã¿ãããšãã§ããŸããéäžã§äœããå¿ããå¯èœæ§ããããŸãã
ããªãã®çããããããšã
@ andrescabana86ããã§ãããªãã¯ãªããžããªãã©ã®çšåºŠåœ¹ç«ã€ãã¯ããããŸãããããã¯ããããã€å ã®ãµãŒããŒã®æ§æã§ããå¯èœæ§ããããŸãã
package.json
ãã®ã¹ã¯ãªããã䜿çšããŠãæ¬çªãã«ãïŒ exportTrailingSlash: true
ã䜿çšïŒãããŒã«ã«ã§ãã¹ãããŠããŸãã
"serve:out": "docker run --rm -v $(pwd)/out:/static -p 5000:80 flashspys/nginx-static"
ãã©ãŠã¶ã§http://localhost:5000/api/test/
ã¢ã¯ã»ã¹ã§ãããã©ããããç¥ãããã ããã
ïŒ $(pwd)
ã¯Mac / Linuxäžã«ããããšã«æ³šæããŠãã ãã-Windowsã«ã€ããŠã¯ãããåç
§ããŠãã ããïŒ
@Izhakiåé¡ã¯ãïŒæåââã®ã¬ããŒãã瀺åããŠããããã«ïŒãæ£åœãªããŒãžã®ãªã³ã¯ã®æ«å°Ÿã®ã¹ã©ãã·ã¥ã¯ã¯ã©ã€ã¢ã³ãåŽã®ããã²ãŒã·ã§ã³ã§ã¯æ©èœããŸããããã³ãã«ãèŠã€ããããããŒããªãã¬ãã·ã¥ïŒssrïŒã§404ã«ãªãããšããäºå®ã«é¢ãããã®ã§ããã ãã®ãããã¯ã©ã€ã¢ã³ãåŽã®ã«ãŒãå€æŽã®åäœãšããŒããªãã¬ãã·ã¥ã®åäœã®éã«äžäžèŽããããŸããã Next.jsã®ææ°ããŒãžã§ã³ã§åé¡ã解決ãããã©ããã¯ããããŸããã ãã¹ãããããããã«å ±åã§ããŸãã
9.4.1ãšexportTrailingSlash: true
ãã¹ãããŸããã
http://localhost:6500/admin/
ã«ç§»åãããšãããŒã«ã«ã§éçºãããšãã«404ãè¿ãããŸãã
ãã ãããšã¯ã¹ããŒãããå Žåãåããã¹ãæ©èœããŸãã
exportTrailingSlash
ã¯ãããã_exports_å°çšã§ããããšã瀺åããŠããããšã«æ³šæããŠãã ããã
ç§ãã¡ãããŠããããšã¯äœ¿çšã§ãïŒ
exportTrailingSlash: process.env.NODE_ENV === 'production'
ã€ãŸããããŒã«ã«ã§éçºããå Žåãç©äºã¯æå³ãããšããã«æ©èœããŸãã ãŸããå±éæã«ïŒãšã¯ã¹ããŒããä»ããŠïŒé©åã«æ©èœããŸãã
ããã¯ããã«å¯Ÿããæ£ãããŠå®è¡å¯èœãªè§£æ±ºçã§ã¯ãããŸãããïŒ
URLãéçºã§ã¯æ©èœããªããæ¬çªã§ã¯æ©èœããå Žåãããã¯é©ãæå°ã®ååã«åãããšæããŸãããïŒ ããã¯ãŸã ãã°ãšèŠãªãããã¹ãã ãšæããŸãã
^ããã¯èšã£ãŠãã以åã®æ¬çªç°å¢ã§ã¯ãããŒãžã®æŽæ°ãšrouter.pushã€ãã³ãã®éã«ç«¶åããåäœããã£ããšç¢ºä¿¡ããŠããŸãã ããããŸã åœãŠã¯ãŸããã©ããã¯ããããŸããã
@ andrescabana86 @Izhaki exportTrailingSlash
ã¯ãããšã¯ç¡é¢ä¿ã§ãã ãã®ãªãã·ã§ã³ã¯ãNext.jsã¢ããªã±ãŒã·ã§ã³ã®éçãšã¯ã¹ããŒãã«é¢é£ããŠããŸãã trueã®å Žåã example/index.html
ãçæãããfalseã®å Žåã example.html
ãçæãããŸãã ç§ã®ç解ã§ã¯ã exportTrailingSlash
ã¯éçºã¢ãŒããšã¯äœã®é¢ä¿ããããŸããã
æ··ä¹±ã®åå ã®1ã€ã¯ã exportTrailingSlash
ãããå Žåãnext.jsããªã³ã¯ã®æ«å°Ÿã«ã¹ã©ãã·ã¥ãè¿œå ããããšã ãšæããŸãã ããã¯éçºã§ãçºçããŸããããããå®è¡ããå¿
èŠããããã©ããããããŸãããïŒ ããããšã«ãããããã¯example/index.html
察example.html
ã ãã§ã¯ãããŸãã-ãªã³ã¯ãå€æŽããå¿
èŠããããŸãã
URLãéçºã§ã¯æ©èœããªããæ¬çªã§ã¯æ©èœããå Žåãããã¯é©ãæå°ã®ååã«åãããšæããŸãããïŒ ããã¯ãŸã ãã°ãšèŠãªãããã¹ãã ãšæããŸãã
ç§ã¯ééã£ãŠãããããããŸããããexportTrailingSlashãªãã·ã§ã³ã¯ãURLã/something
ãšãã«/something.html
ãæäŸããããã«æ§æãããŠããªãnginxãµãŒããŒçš/something
ã
ããã¯ãããŒã«ã«éçºã«äœ¿çšããã次ã®ãµãŒããŒã«ã¯åœãŠã¯ãŸããŸããã ãããã£ãŠãäœãæ©èœããäœãæ©èœããªããã¯ãã¢ããªã«æäŸããããã®ã«ãã£ãŠç°ãªããŸãã
ããªãã¯æã«ããå Žåè¡ãããšãã§ããŸãexportTrailingSlash
çã§ããïŒããã¯è¡ããŸããã次ã®ãµãŒãã«ã¯ãæ«å°Ÿã«ã¹ã©ãã·ã¥ã§çµããã«ãŒãããµããŒãããå¿
èŠããããŸãexport
ã§exportTrailingSlash
ããç¡é¢ä¿ïŒã
FWIWããã¯ãã§ã«åãçµãã§ããŸãïŒ13333
ç§ã¯çµéšè±å¯ãªã³ãŒããŒã§ã¯ãªããäž»ã«è€æ°ããŒãžã®ã©ã³ãã£ã³ã°ã«Next.jsã䜿çšããŠããŸãã ã©ããããç§ã¯ãã®å¹æã«æ°ã¥ããã«ãã»ãšãã©ãã€ã次ã®åé¿çã䜿çšããŠããŸããã ããããã®ç°¡ç¥çã§ãïŒ
// In your server.js
server.get('/:id', (req, res) => {
const actualPage = `/${req.params.id}`
app.render(req, res, actualPage)
})
ç§ã®å Žåãè¿œå ã®éçURLãã¬ãã£ãã¯ã¹ãªã©ããµããŒãããããã«äœ¿çšããŠãããããã³ãŒãã¯å°ãè€éã§ãããããããã®åé€ãããããŒãžã§ã³ã¯ã exportTrailingSlash
ã«é¢ä¿ãªãã説æããåé¡ã«å¯ŸããŠåé¡ãªãæ©èœããŠããããã§ããèšå®ãšãã®Link
ã®åœ±é¿ã ããšãã°ãURL /about
ãš/about/
åé¡ãªãæ©èœããŸãã
çŸåšã®åœ¢åŒã§ã¯ãåºæ¬çã«Next.jsã®ãã€ãã£ãã«ãŒãã£ã³ã°ãæš¡å£ããŠããŸãã æ¬ ç¹ã¯ïŒããã¯ã«ã¹ã¿ã å¿
èŠã§ãserver.js
ãäŸãã°ïŒè¿œå ã®ããµããã©ã«ããã§ïŒããããŠæåã§ãããæ·±ããã®URLã®ããã«ããããµããŒãããå¿
èŠããããŸã/company/about/
ã ãããããããžã§ã¯ãã§ã«ã¹ã¿ã server.js
ããã§ã«äœ¿çšããŠãã人ã«ãšã£ãŠã¯ãæ¯èŒçç°¡åãªè§£æ±ºçã®ããã§ãã
ãããSSRã§æ©èœãããã«ã¯ã @ pjawsããã³@AlexSapoznikovãœãªã¥ãŒã·ã§ã³ã«ä»¥äžãè¿œå ããå¿ èŠããããŸããã
static async getInitialProps({ Component, ctx, router }) { /* Fixes the trailing-slash-404 bug for server-side rendering. */ const { asPath } = router; if (asPath && asPath.length > 1) { const [path, query = ""] = asPath.split("?"); if (path.endsWith("/") && path.length > 1) { const asPathWithoutTrailingSlash = path.replace(/\/*$/gim, "") + (query ? `?${query}` : ""); if (ctx.res) { ctx.res.writeHead(301, { Location: asPathWithoutTrailingSlash, }); ctx.res.end(); } } } return { pageProps: Component.getInitialProps ? await Component.getInitialProps(ctx) : {}, }; }
ããããããã®æ©èœãSSRãšCSRã®äž¡æ¹ã§æ©èœããé¢æ°ã«äžè¬åããäž¡æ¹ã®å ŽæïŒ
getInitialProps
ãšrender
ïŒã§åŒã³åºãããšããå§ãããŸããããã¯getServerSidePropsã®ããããŒãžã§æ©èœããæ«å°Ÿã«ã¹ã©ãã·ã¥ãä»ããURLã¯404ãªãã§åãããŒãžãè¿ãããã«ãªããŸããã
ããããã°ãªããã1ã€ãããŸããåçã«ãŒããšgetStaticPathsã䜿çšããããŒãžãã»ãšãã©ãªããããgetServerSidePropsã䜿çšã§ããªãããããããã®åçã«ãŒããæ«å°Ÿã®ã¹ã©ãã·ã¥ã§åç §ãããšãæåã«404ãè¿ããã次ã«ããŒãžã«ãªãã€ã¬ã¯ããããŸãã ã
@gauravkrpããã¯å®éã«ã¯éåžžã«éèŠãªè¿œå ã§ãã @ AlexSapoznikovãœãªã¥ãŒã·ã§ã³ã¯å®éã«ã¯ããŒãžã®404ãGoogleã«è¿ãããã§ãïŒãªãã€ã¬ã¯ãã¯ã¯ã©ã€ã¢ã³ãã§çºçããããïŒã ç§ãã¡ã®å€ããããããNext.jsã䜿çšããŠããäž»ãªçç±ã¯SEOã ãšæããŸãã
ãŸãããããgetInitialProps
å
¥ããã ãã§ããŸãããã¯ãã§ããããã®æç¹ã§ã¯mainé¢æ°å
ã®éšåã¯äžèŠã ãšæããŸãã ããã§ã®äž»ãªæ³šæç¹ã¯ãããã䜿çšãããšèªåéçæé©åã倱ãããããšã§ãããã ãããããã404ã®æãããåªããŠããŸãã
ããã€ãã®å ±æã®ããã«...
ç§ã®ãããžã§ã¯ãã¯Express
+ Next.js
ã§ãã
express 4.17.1
next 9.4.5-canary.7
åçã©ã³ã¿ã€ã
// next.config.js
module.exports = {
exportTrailingSlash: false,
};
// app.js
const Next = require('next').default;
const NextApp = Next({ dev });
const NextHandler = NextApp.getRequestHandler();
NextApp.prepare();
app.get('*', (req, res) => NextHandler(req, res));
éçãšã¯ã¹ããŒã
next build
ãšnext export -o dist/
// next.config.js
module.exports = {
exportTrailingSlash: true,
};
// app.js
app.use('/_next', express.static('dist/_next', { etag: true, index: false, maxAge: '365d', redirect: false, dotfiles: 'ignore' }));
app.use('/fonts', express.static('dist/fonts', { etag: true, index: false, maxAge: '365d', redirect: false, dotfiles: 'ignore' }));
app.use('/img', express.static('dist/img', { etag: true, index: false, maxAge: '365d', redirect: false, dotfiles: 'ignore' }));
app.use(express.static('./dist', { index: ['index.html'] }));
app.use((req, res) => {
res.Redirect('/404'); // <- Express will auto handle both /404 or /404/
});
ã¯ã©ã€ã¢ã³ãã¢ããªãã¯ãªãã¯ããŠãªãã€ã¬ã¯ãããŠãåé¡ãããŸãããã
ãŸããããŒããªãã¬ãã·ã¥ã¯static route
ãŸãã
ãã ãã dynamic route
ã§ããŒããªãã¬ãã·ã¥ãããš404ã«ãªããŸãã
/album/[id].jsx
ã/album/123
ã
ãããã£ãŠã次ã®ã¡ã«ããºã ã䜿çšããŠãã®åé¡ãä¿®æ£ããããšã楜ãã¿ã«ããŠããŸãã
äŸãã°
/album/123
ã§404ãããããããšã
ãµãŒããŒã¯åŒãç¶ãHTMLã³ã³ãã³ããæäŸããå¿
èŠããããŸãã
ãã©ãŠã¶ã¯åé¡ãªãããŒãžãããŒããç¶ããŸãã
Next.jsãèµ·åãããšã next/router
ãèªåçã«åŠçããŸãã
æ¬çªç°å¢ã§ãã®åé¡ã®äžæçãªè§£æ±ºçã¯ãããŸããïŒ
ãããä¿®æ£ããæ©èœã1æ¥ã»ã©çéžãããããšããŠããŸãã
æ¬çªç°å¢ã§ãã®åé¡ã®äžæçãªè§£æ±ºçã¯ãããŸããïŒ
ãã®ã¹ã¬ããã«ã¯ãããããããŸãããç§ã¯çŸåš@gauravkrpãæè¿æçš¿ãããã®ã䜿çšããŠãããããã¯ç§ã«ãšã£ãŠããŸãæ©èœããŠããŸãã
ããã§PRã远跡ã§ããŸãïŒïŒ13333
ããã¯next@^9.4.5-canary.17
解決ãããŸããïŒ
æ©èœãã«ããªã¢ãããã¹ã¿ãŒã«å°éãããŸã§ã«ã©ã®ãããæéãããããŸããïŒ
ããã¯
next@^9.4.5-canary.17
解決ãããŸããïŒ
ãããŠãããã¯ã©ã®çšåºŠæ£ç¢ºã«è§£æ±ºãããŸããïŒ æ«å°Ÿã®ã¹ã©ãã·ã¥ãåé€ããã ãã§ããïŒ ç§ã®ã¢ã¯ã»ã¹ãå Žåwww.site.com/help/ ãç§ã¯ã«ãªãã€ã¬ã¯ãïŒã www.site.com/help ããæã ã¯ã¹ã©ãã·ã¥ãçµäºå»ãããã«ããã«éžã¶éžæè¢ãæã€ããšãã§ããŸããïŒ ã www.site.com/help/ ããŸãã¯ã www.site.com/help ãã«ã¢ã¯ã»ã¹ãããšãã/ããçµäºãããããªãã€ã¬ã¯ããããããæåŸã«ã/ããè¿œå ãããã www.site.com/help/ ãã«ãªããŸãã
@Valnexusã¯ïŒ
module.exports = {
experimental: {
trailingSlash: true
}
}
æ©èœãã«ããªã¢ãããã¹ã¿ãŒã«å°éãããŸã§ã«ã©ã®ãããæéãããããŸããïŒ
æºåãã§ãããã 解決ãããŠããåŠçã«ã¯ãŸã ãšããžã±ãŒã¹ããããŸãã ããããä¿®æ£ããããšãå®å®ããããšãã§ããŸãã
@timneutkens @Janpot
ç§ã¯ãææ°ã®æ¬¡ã®ã«ããªã¢ïŒ9.4.5-canary.27ïŒãè©ŠããŠã¿ãŸããããç§ã¯äœæãããšãã«test
ããŒãžããç§ã¯ã¢ã¯ã»ã¹www.example/test/
ããã¯ã«ãªãã€ã¬ã¯ãwww.example/test
ã©ã¡ãã®å Žåãåãããã«åäœããã¯ãã ãšæããŸãã
www.example/test/
ã«ã¢ã¯ã»ã¹ãããšãã¯ã www.example/test/
ãŸãŸã«ããå¿
èŠããããŸãã
www.example/test
ã«ã¢ã¯ã»ã¹ãããšãã¯ã www.example/test
ãŸãŸã«ããå¿
èŠããããŸãã
Nuxt.jsã§ãã¹ãããŸãããäžèšãšåãåäœãããŸãã
ã©ã¡ãã®å Žåãåãããã«åäœããã¯ãã ãšæããŸãã
ãªãã€ã¬ã¯ãã®çç±ã¯ãæ€çŽ¢ãšã³ãžã³ã«éè€ããã³ã³ãã³ãã衚瀺ãããªãããã«ããããã§ãã ããªãã®æ£ç¢ºãªãŠãŒã¹ã±ãŒã¹ã¯äœã§ããïŒ
ãŸã å®å®çã«ããŒãžãããŠããªãã®ã«ããªãã¯ããŒãºããªåé¡ãªã®ãããããŸããã ç§ãæ£ããç解ããŠããã°ãããã¯ä»ã®ãšããã«ããªã¢ãªãªãŒã¹ã§ã®ã¿ä¿®æ£ãããŠããŸãããïŒ
ã«ããªã¢ã§ããã«äœ¿çšã§ãããããé¢é£ãããã«ãªã¯ãšã¹ããå°çãããšåé¡ã¯ã¯ããŒãºãããŸãã ãã®æ©èœãå¿ èŠãªå Žåã¯ãã«ããªã¢ãã£ã³ãã«ã«ã¢ããã°ã¬ãŒãããŠãã ããã
ããã§ããã ããããšãã@ TimerïŒ
@Janpot https://github.com/issues/
ãšhttps://github.com/issues
ããªãã€ã¬ã¯ããªãã§åãåäœã«ã¢ã¯ã»ã¹ã§ããããšã確èªããŸããã
https://twitter.com/explore/
ãšhttps://twitter.com/explore
ããããã
æ€çŽ¢ãšã³ãžã³ã«åé¡ãããå ŽåãGithubãšTwitterã§ä¿®æ£ãããªãã£ãã®ã¯ãªãã§ããïŒ
ããã¯ã©ã®Webãµã€ãã§ãããã©ã«ãã®åäœã ãšæããŸãã
ç¹å®ã®ãŠãŒã¹ã±ãŒã¹ã¯ãããŸããããã®ããã«æ©èœããã¯ãã ãšããã®ãç§ã®æèŠã§ãã
æ€çŽ¢ãšã³ãžã³ã«åé¡ãããå ŽåãGithubãšTwitterã§ä¿®æ£ãããªãã£ãã®ã¯ãªãã§ããïŒ
@armspktããã解決ããããã€ãã®æ¹æ³ãããã®ã§ãããã¯åé¡ã§ã¯ãããŸããã ããšãã°ãTwitterã¯<link rel="canonical">
å±æ§ã䜿çšããŠãæ€çŽ¢ãããã«ã¯ããŒã«ããããŒãžãšä»ã®ããŒãžã§ã³ãéè€ãšããŠããŒã¯ããå¿
èŠãããããšãéç¥ããŸãã
ãããã£ãŠããªãã€ã¬ã¯ãã¯ãWebãµã€ãã§SEOãäœæããããã®å®è¡å¯èœãªæ¹æ³ã§ãã ããªãã¯ããã§ããå€ãã®æ å ±ãèªãããšãã§ã
@zisermanããã解決ããæ¹æ³ãããã€ãããå Žåã¯ããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã®ããã«ãªãã€ã¬ã¯ãããã«åãURLãç¶æããå¿ èŠããããŸãã
@Janpot https://github.com/nuxt-community/nuxt-i18n/issues/422
Nuxtjsã«ã¯ããã€ãã®ãªãã·ã§ã³ããããŸãïŒæªå®çŸ©ãtrueãfalseïŒ
Nextjsã«ããµãŒããŒãªãã·ã§ã³ãéžæããå¿ èŠããããŸããïŒ
ãªãã€ã¬ã¯ãã®çç±ã¯ãæ€çŽ¢ãšã³ãžã³ã«éè€ããã³ã³ãã³ãã衚瀺ãããªãããã«ããããã§ãã ããªãã®æ£ç¢ºãªãŠãŒã¹ã±ãŒã¹ã¯äœã§ããïŒ
@Janpotç§ãã¡ã®APIã«ã¯ãå€ãã®å Žæã§æ«å°Ÿã«ã¹ã©ãã·ã¥ããããŸãã ææ°ã®ãªãªãŒã¹ã§ã¯ãæ«å°Ÿã«ã¹ã©ãã·ã¥ãä»ããURLïŒ/ api / test /-> / api / testïŒãäžèŽããªããããããã¯ãšã³ãã§å€ãã®404ãçºçããŸãã
ããããã¹ãŠã®äººã«åœ¹ç«ã€ãã©ããã¯ããããŸããããç§ã¯ãã®è§£æ±ºçãç§ã«åœ¹ç«ã€ããšãçºèŠããŸããã _app.js
ãã¡ã€ã«ã«å
¥ããŸãã
static async getInitialProps(ctx) {
const appProps = await App.getInitialProps(ctx);
// Remove trailing slash
const path = ctx.router.asPath,
res = ctx.ctx.res;
if (path.length > 1 && /\/$/.test(path)) {
res.writeHead(301, {Location: path.slice(0, -1)})
res.end();
}
return {...appProps};
}
@mlbonniec Next.jsã¢ããªã§æ·±å»ãªããã©ãŒãã³ã¹ã®äœäžãåŒãèµ·ãããããã³ã¡ã³ããæå°éã«æããŸããã
ææ°ã®next@canary
ããŒãžã§ã³ã¯ãã®ãã°ãä¿®æ£ããŠããŸãã代ããã«ã¢ããã°ã¬ãŒãããŠãã ããïŒ
@mlbonniec Next.jsã¢ããªã§æ·±å»ãªããã©ãŒãã³ã¹ã®äœäžãåŒãèµ·ãããããã³ã¡ã³ããæå°éã«æããŸããã
ææ°ã®
next@canary
ããŒãžã§ã³ã¯ãã®ãã°ãä¿®æ£ããŠããŸãã代ããã«ã¢ããã°ã¬ãŒãããŠãã ããïŒ
åé¡ãªãïŒ
ããããç§ã¯ä»¥åã«æŽæ°ããŸããããããŠããã¯åé¡ã解決ããŸããã§ããã
npm update
ææ°ã®Next.jsã«ããªã¢ã§ãã°ãä¿®æ£ãããªãå Žåã¯ãæ°ããåé¡ãéããŠç¢ºèªããŠãã ããã ð
ç°¡åãªè³ªåã§ããã next export
ãããžã§ã¯ãã¯ãã®å€æŽãã©ã®ããã«åŠçããŸããïŒ æ«å°Ÿã®ã¹ã©ãã·ã¥ã®ããŒãžããšã«ãŸã£ããæ°ããããŒãžãäœæããããšã«ãã£ãŠïŒ ãšã¯ã¹ããŒããããã¢ããªãHTTPãªãã€ã¬ã¯ãïŒãŸãã¯æžãæãïŒãæå®ã§ãããšã¯æããŸããã
next export
ã䜿çšãããããžã§ã¯ãã§ã¯ãã¯ã©ã€ã¢ã³ãåŽã®ãã¹ãŠã®<Link />
ãæ£ããæŽæ°ãããŸããããµãŒããŒåŽã®ãªãã€ã¬ã¯ãã§ã¯æåã§æ§æããå¿
èŠããããŸãã ãµãŒããŒã¬ã¹ã¿ãŒã²ãããŸãã¯next start
ãããã€ããããããžã§ã¯ãã¯ããããã®èšå®ãèªåçã«æ§æããŸãã
@Timerãããå®å šãªãªãªãŒã¹ã«éããåŸã§ããå®éšçãªãªãã·ã§ã³ã䜿çšããå¿ èŠããããŸããïŒ
@Timerãããå®å šãªãªãªãŒã¹ã«éããåŸã§ããå®éšçãªãªãã·ã§ã³ã䜿çšããå¿ èŠããããŸããïŒ
ãããããã®ãŸãŸå©çšã§ããŸãã
trailingSlash
ãªãã·ã§ã³ã¯next export
ãªããšæããŸããïŒ ããšãã°ãgithubããŒãžã§/page/
ã/page
ïŒãŸãã¯ãã®éã«ïŒãªãã€ã¬ã¯ãããæè¯ã®æ¹æ³ã¯äœã§ããïŒ
trailingSlash
ãªãã·ã§ã³ã¯next export
ãªããšæããŸããïŒ ããšãã°ãgithubããŒãžã§/page/
ã/page
ïŒãŸãã¯ãã®éã«ïŒãªãã€ã¬ã¯ãããæè¯ã®æ¹æ³ã¯äœã§ããïŒ
ç§ã®ç¥ãéããgithubããŒãžã«ã¯ãªãã€ã¬ã¯ãæ©èœããããŸããã ããã¯vercel.comã§ç®±ããåºããŠããã«æ©èœããŸããã趣å³ã®ãããžã§ã¯ãã§ãç¡æã§ãïŒgithubããŒãžã®ããã«ïŒã
next export
ã䜿çšãããããžã§ã¯ãã§ã¯ãã¯ã©ã€ã¢ã³ãåŽã®ãã¹ãŠã®<Link />
ãæ£ããæŽæ°ãããŸããããµãŒããŒåŽã®ãªãã€ã¬ã¯ãã§ã¯æåã§æ§æããå¿ èŠããããŸãã ãµãŒããŒã¬ã¹ã¿ãŒã²ãããŸãã¯next start
ãããã€ããããããžã§ã¯ãã¯ããããã®èšå®ãèªåçã«æ§æããŸãã
ããã«ã¡ã¯@Timer詳ãã説æããŠããã ããŸããïŒ æåã§æ§æããã«ã¯ã©ãããã°ããã§ããïŒ ãããç§ã®ç¶æ³ã§ãã ç§ã®ãŠã§ããµã€ãã§ã¯ã next-i18next
ã䜿çšããŠããŸãã next build && next export
ã§ãããã€ããåŸããã¹ãŠã®å
éšãªã³ã¯ã¯æ©èœããŸãããæåã§URLãå
¥åãããšãã©ããæ©èœããã404ãšã©ãŒãçºçããŸãã ããããtrailingSlash:true
ã䜿çšããããšã«ããã®ã§ãæåã§/pricing
ãšå
¥åãããšæ©èœããŸããã /zh/pricing
404ãšã©ãŒãçºçããŸãã
æãåèã«ãªãã³ã¡ã³ã
ãããä¿®æ£ããæ©èœã1æ¥ã»ã©çéžãããããšããŠããŸãã