ํฉ๋ฒ์ ์ธ ํ์ด์ง์ ๋ํ ๋งํฌ์ ํํ ์ฌ๋์๋ ํด๋ผ์ด์ธํธ ์ธก ํ์์๋ ์๋ํ์ง๋ง ํ๋ ์๋ก ๊ณ ์นจ(ssr) ์ ๋ฒ๋ค์ ์ฐพ์ ์ ์๊ณ 404๋ก ์ด์ด์ง๋๋ค.
์ ๋ชฉ์ ์ถ๊ฐ ์ค๋ช ์ด ํ์ํ ๊ฒฝ์ฐ ์๋ ค์ฃผ์ญ์์ค.
๋ชจ๋ ๊ด๋ จ ๋ฌธ์ ๋ 6-์นด๋๋ฆฌ์(๊ทธ๋ ์ง ์๋ค๊ณ ์๊ฐํจ) ๋๋ ํฅ์๋ ์๋ธ(์๋ง๋ ํ๋ก๋์ ์ ์ ๋ด๋ณด๋ด๊ธฐ์์๋ง ํด๋น๋จ)๋ก ์์ ๋์๋ค๋ ์ถ๋ก ์ผ๋ก ๋ง๊ฐ๋์์ต๋๋ค.
๊ธฐ์กด ๋ธ๋ก๊ทธ๋ฅผ next.js๋ก ๋ค์ ์์ฑ ์ค์ด๋ฉฐ ์ด์ ์ ํํ ์ฌ๋์๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ๋ด next.js ๊ธฐ๋ฐ ๋ธ๋ก๊ทธ๋ฅผ ๊ตฌ์ถํ๋ฉด ์ต์ serve
๊ฐ ๋์์ด ๋ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ dev env๋ฅผ ์์ ํ๋ ค๋ฉด ํํ ์ฌ๋์๋ฅผ ์ ๊ฑฐํ๊ณ prod์์ 301 Moved Permanently
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๋๋ dev์์ ๋์ด์ง ํํ ์ฌ๋์ ์ง์์ผ๋ก ์ด๊ณ ์์ต๋๋ค.
๋ค์์ ์ฌํ ๊ฐ๋ฅํ ์ต์ ์ฌ๋ก์ ๋๋ค(repro repo์ ๋ํ ๋งํฌ๋ ์ค๋ํซ ์๋์ ์์).
// 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
๊ฐ 200์ด ๋๋ ๊ฒ์ ๊ด์ฐฐํ๋คhttp://localhost:3000/_next/on-demand-entries-ping?page=/about/
๊ฐ 200์ด ๋๋ ๊ฒ์ ๊ด์ฐฐํ๋คhttp://localhost:3000/about/
์ด 404ed์ธ ๊ฒ์ ๊ด์ฐฐํ์ญ์์ค.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 link๋ฅผ ํด๋ฆญํ ๋ค์:
/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 ์์ ์ฌํํ ์ ์์ต๋๋ค.
์ ๋ nextjs 7์ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ ํํ ์ฌ๋์๋ 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์์๋ ๋ฌธ์ ๊ฐ ๋์ง ์์ง๋ง ๊ฐ๋ฐ(Next 7)์์๋ ํํ ์ฌ๋์๊ฐ ์์ผ๋ฉด ์ปดํ์ผ์ด ์ค์ง๋๊ณ ์ด์ ๋ฅผ ํ์ ํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค. ๋๋ ์ด๊ฒ์ด (๊ฐ๋ฐ ํ๊ฒฝ์์ ํํ ์ฌ๋์๋ฅผ ์ฒ๋ฆฌํ์ง ์์) ์ข์ DX๋ผ๊ณ ์๊ฐํ์ง ์์ต๋๋ค.
์ ๋ ์ด ๋ฌธ์ ๋ฅผ ๊ฒช๊ณ ์๋๋ฐ ์ ๋ง ์ง์ฆ๋๋ค์. ๋นจ๋ฆฌ ํด๊ฒฐ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
ํํ ์ฌ๋์๋ฅผ ์ํ๋ฉด ์ด๋ ๊ฒ ํ๋ฉด ๋ฉ๋๋ค. <Link href='/about' as='/about/'><a>about</a></Link>
ํ์ง๋ง ๊ธ์์ผ/๋ค์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ๋์ trailingSlash
๋ฅผ ์ํ์ผ๋ก ์ถ๊ฐํ ์ ์๋ ํฌํฌ ๊ฐ ์์ต๋๋ค. ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค
ํํ ์ฌ๋์๋ฅผ ์ํ๋ฉด ์ด๋ ๊ฒ ํ๋ฉด ๋ฉ๋๋ค.
<Link href='/about' as='/about/'><a>about</a></Link>
ํ์ง๋ง ๊ธ์์ผ/๋ค์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ๋์trailingSlash
๋ฅผ ์ํ์ผ๋ก ์ถ๊ฐํ ์ ์๋ ํฌํฌ ๊ฐ ์์ต๋๋ค. ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค
@aluminick ์ฃ์กํฉ๋๋ค. ๋ฐฉ๊ธ ์๋ํ์ง๋ง ์๋ํ์ง ์์ต๋๋ค. ๋๋ ์ฌ์ ํ ์๋ก ๊ณ ์นจ(ํ์ฌ ๋์) ํ์ ์ฐพ์ ์ ์๋ traling-slashed ํ์ด์ง(์ต์ ๋ฆด๋ฆฌ์ค)์ ๋๋ฌํฉ๋๋ค.
experimental.exportTrailingSlash
๋ next export
์ ์ฉ์ด๊ธฐ ๋๋ฌธ์ ๋์์ด ๋์ง ์์ต๋๋ค.
@Janpot ์ ์ ๋งํ pull ์์ฒญ #6421์ด
@iamstarkov ์ด ๋ฌธ์ ์ ์ํ๋ ๋ฌด์์
๋๊น? server.js
ํํฌ ์ธ์ ์๋ฃจ์
์ด ์์ต๋๊น?
@dryleaf ์ํ: ์์ง ์ด๋ ค ์์ต๋๋ค.
์ ์ฌํ ๋ฌธ์ ... ์ฌ๋์๊ฐ ์ฌ๋ฌ ๊ฐ ์ถ๊ฐ๋๋ฉด ๋ฆฌ๋๋ ์ ๋ฉ๋๋ค. ์: https://github.com/zeit/next.js/////////////issues/5214
GitHub URL์ ๊ด๋ จ์ด ์์ต๋๋ค.
@iamstarkov ๋ฌด์จ ๋ง์ธ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ๊ทธ๋ฌ๋ ์๋ ๊ฒ์๋ฌผ์ ๋ค์ ์ฝ์ ํ ๋ ๋ช ํํด์ง ์ ์์๋ ๊ฒ ๊ฐ์ต๋๋ค.
GitHub URL์ ์ฑ์ด Next.js๋ก ๋น๋๋ ๋ URL์ด (๊ฐ๊ธ์ ) ์ด๋ป๊ฒ ์๋ํด์ผ ํ๋์ง์ ๋ํ ๊ฐ๋จํ ๋ฐ๋ชจ๋ฅผ ์๋ฏธํฉ๋๋ค. ์ฆ, ์ฌ์ฉ์๊ฐ ์ฌ๋์๋ฅผ ์ถ๊ฐํด๋ URL์ ๊ณ์ ์๋ํด์ผ ํฉ๋๋ค.
nextjs 9์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น?
์ ๋ Next๋ฅผ ์ฒ์ ์ฌ์ฉํ์ง๋ง ์ด ๋ฌธ์ ์ ๋ํด ์ฌ๋ฌ๋ถ์ด ์ฌ์ฉํ๊ณ ์๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ฌด์์ ๋๊น?
@iamstarkov ์ด ๋ฌธ์ ์ ์ํ๋ ๋ฌด์์ ๋๊น?
๋๋ ์ด ๋ฌธ์ ๊ฐ ์ฝ 1๋
๋์ ์ด๋ค ์์ผ๋ก๋ ํด๊ฒฐ๋์ง ์์๋ค๋ ์ฌ์ค์ ์ถฉ๊ฒฉ์ ๋ฐ์์ต๋๋ค!
Next.js ํ์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ค๋ฅธ ์ด์ ๊ฐ ํ์ํฉ๋๊น?
URL์ ํํ ์ฌ๋์์ ์๊ด์์ด ์๋ํด์ผ ํฉ๋๋ค. ์น์์ ์๋ฌด ์ฌ์ดํธ๋ ํ์ธํ์ญ์์ค.
์ด๊ฒ์ด Next.js์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๊ฒฝ์ฐ ์ง๊ธ์์ ๊ตฌ์ฑํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
Zeit ํ์ด ๋ช ๋
๋์ ๊ทธ๋ฌํ ์ค์ํ ๋ฌธ์ ๋ฅผ ๋ฌด์ํ๋ค๋ ์ฌ์ค์ด ์ ๋ง ํผ๋์ค๋ฝ์ต๋๋ค.
@exentrich ์ด๊ฒ์ ๋ชจ๋ ํํ ์ฌ๋์๋ฅผ ์ฌ๋์ ์์ด ๋์ผํ ๊ฒฝ๋ก๋ก ๋ฆฌ๋๋ ์ ํจ์ผ๋ก์จ Zeit Now์์ ์ฝ๊ฒ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
now.json
:
"routes": [
{
"src": "/(.*)/",
"status": 301,
"headers": { "Location": "/$1" }
},
...
]
๊ทธ๋ฌ๋ ์ ์ด๊ฒ์ด Next.js ์์ฒด์์ ์ฒ๋ฆฌ๋์ง ์๊ณ ํ์ด ์ด ๋ฌธ์ ๋ฅผ ๋ฌด์ํ๋์ง ์ดํดํ์ง ๋ชปํฉ๋๋ค.
์ด๊ฒ์ public/
(์์
์ค)์ ํจ๊ป CRA ๋ณํ์ด ์คํ๋๋ ์ฃผ์ ๋ฌธ์ ์
๋๋ค.
@rauchg
@NathanielHill ๊ฐ์ฌํฉ๋๋ค!
์ด ์๋ฃจ์
์ ์๋ํ์ง๋ง ์ฟผ๋ฆฌ ๋งค๊ฐ ๋ณ์๊ฐ ์ ๊ฑฐ๋์์ต๋๋ค. ์๋ฅผ ๋ค์ด /some/?query=1
๋ ์ฟผ๋ฆฌ ์์ด /some
๋ฆฌ๋๋ ์
๋ฉ๋๋ค. ๋น์ ์ ๊ทธ๊ฒ์ ๊ณ ์น ๋ฐฉ๋ฒ์ ์๊ณ ์์ต๋๊น?
์, ๋ฌธ์ ์ธ ๊ฒ ๊ฐ์ต๋๋ค @exentrich
์ ๊ท์ ์ฃผ์์ ์์์ ^
๋ฐ $
๊ฐ ์๋ค๋ ๋ง์ ๋ค์์ ๋ ๊ทธ ๋์์ ์ถ์ธกํ์ง ๋ชปํ์ ๊ฒ์
๋๋ค(๊ทํ์ ์๊ฐ ์ผ์นํ์ง ์์์ ์๋ฏธํจ). ๋ค์ ์ถ๊ฐํ๊ธฐ ์ํด ์์ฒด์ ์ผ๋ก ์ฟผ๋ฆฌ ๋ฌธ์์ด์ ์ก์ธ์คํ๋ ๋ฐฉ๋ฒ์ด ์์ ์ ์์ต๋๋ค.man_shrugging: ํ์ด์ ๋น๋๋ค
์ฌ์ฉ์ ์ง์ ์ต์คํ๋ ์ค ์๋ฒ์ avinoamr/connect-slash๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ํค๋ ค๊ณ ํ์ง๋ง ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ ํนํ /
๊ฒฝ๋ก๊ฐ ์ค๋ฅ ํ์ด์ง๋ฅผ ๋ฐ์์ํค๊ณ SEO(Next์ ์ฃผ์ ๋ฌด์น๋ถ ์ค ํ๋)์ ํผํด๋ฅผ ์ฃผ๊ธฐ ๋๋ฌธ์ ํ์คํ ํฐ ๋ฌธ์ ์
๋๋ค.
301 ๋ฆฌ๋๋ ์ ๋ฐ ์ฌ์ฉ์ ์ง์ ์ต์คํ๋ ์ค ์๋ฒ๋ ๋ชจ๋ ์์ ์ด ์๋๋ผ ํดํน์ธ ๊ฒ ๊ฐ์ต๋๋ค. ์ ๊ฒฝ์ฐ ์๋ ์ฌ์ฉ์ ์ ์ Express ์๋ฒ ์์ด Next์ ์๋ฒฝํ๊ฒ ์๋ํ๋ ์์ฉ ํ๋ก๊ทธ๋จ์ด ๊ตฌ์ถ๋์ด ์์ต๋๋ค. ๋ค๋ฅธ ๋ชจ๋ ๊ฒ์ ์๋ฒฝํ๊ฒ ์๋ํ์ง๋ง ์ง๊ธ์ ํํ ์ฌ๋์ ๋ฌธ์ ๋๋ฌธ์ ์ Express ์๋ฒ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ์ด๊ฒ์ด ํดํน์ด๋ผ๋ ์ ์ ๊ฐ์ํ ๋ ํ์ํ ๋ ธ๋ ฅ์ด ๊ณผ๋ํ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๊ฒ์ด ์ฐ์ ์์๊ฐ ๋ ์ ์๋ค๋ฉด ์ข๊ฒ ์ต๋๋ค! ์ด๋ฌํ ์ด์ ๋๋ฌธ์ ๋๋ ์ฐ๋ฆฌ ํ์์ ๋ฐ๋๋ผ React/Angular์ ๋ฐ๋๋ก Next๋ฅผ ์ฌ์ฉํ๋ค๋ ๋ถํ์ ๋ฃ๊ณ ์์ผ๋ฉฐ ๋ถ๋ช ํ Next์ ๊ฒฝ์ฐ๋ฅผ ์ฝํ์ํต๋๋ค.
์ถ์ : ์ ๋ Next์ ์์ ํ๋ ๊ฒ์ด ์ ๋ง ์ข์ต๋๋ค โค๏ธ
์ด๊ฒ์ ํนํ
/
๊ฒฝ๋ก๊ฐ ์ค๋ฅ ํ์ด์ง๋ฅผ ๋ฐ์์ํค๊ณ SEO์ ํผํด๋ฅผ ์ฃผ๊ธฐ ๋๋ฌธ์ ํ์คํ ํฐ ๋ฌธ์ ์ ๋๋ค.
๊ทธ๊ฒ์ ๋น์ ์ SEO๋ฅผ ํด์น์ง ์์ต๋๋ค. Google์ ํํ ์ฌ๋์๋ฅผ ๋ค๋ฅธ ํ์ด์ง๋ก ์ทจ๊ธํฉ๋๋ค. 404๋ก ์ค์ ํด๋ ์ฌ์ดํธ์ ์กด์ฌํ์ง ์๋ ๋ค๋ฅธ ํ์ด์ง๋ณด๋ค ๋ ์ด์ SEO์ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค. ๊ฒ๋ค๊ฐ ํํ ์ฌ๋์๋ก ๋งํฌํ์ง ์๋ ํ Google์ ์ฒ์๋ถํฐ ํฌ๋กค๋ง์ ์๋ํ์ง ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ ์ฌ์ ํ ์ ํจํ ๋ฌธ์ ์ด์ง๋ง ์ฌ๋ฌ๋ถ ๋ชจ๋๊ฐ ์๊ฐํ๋ ๊ฒ๋ณด๋ค ๋ ์ค์ํฉ๋๋ค.
@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}`);
})();
@์ํ
๊ทธ๊ฒ์ ๋น์ ์ SEO๋ฅผ ํด์น์ง ์์ต๋๋ค. Google์ ํํ ์ฌ๋์๋ฅผ ๋ค๋ฅธ ํ์ด์ง๋ก ์ทจ๊ธํฉ๋๋ค. 404๋ก ์ค์ ํด๋ ์ฌ์ดํธ์ ์กด์ฌํ์ง ์๋ ๋ค๋ฅธ ํ์ด์ง๋ณด๋ค ๋ ์ด์ SEO์ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค.
๋๋ ๊ทธ๊ฒ์ด ๋ณธ์ง์ ์ผ๋ก SEO๋ฅผ ํน๋ณํ ํด์น์ง ์๋๋ค๋ ์ ์ ์ง์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ๋ฐ์๋ ๋งค๋ฒ ์ฌ๋ฐ๋ฅธ URL ์ ์๋ฅผ ์ป์ด์ผ ํ๋ฏ๋ก ์ฌ๋์ ์ค์๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. Next๋ฅผ ์ฒ์ ์ ํ๋ ๊ฐ๋ฐ์๋ ๋ค์(์์ ํ ์ ์์ ์ธ ๋ชจ์) URL์ด 404 ํ์ด์ง๋ก ์ฐ๊ฒฐ๋๋ค๋ ์ฌ์ค์ ์์ง ๋ชปํ ๊ฒ์
๋๋ค. <Link href='/people/'>
์ฑ์ํ ํ๋ ์์ํฌ๋ ์ด์์ ์ผ๋ก๋ ๊ทธ๋ฌํ ์ธ์ ์ค๋ฅ์ ๋์์ด ๋์ด์๋ ์ ๋ฉ๋๋ค.
๊ฒ๋ค๊ฐ ํํ ์ฌ๋์๋ก ๋งํฌํ์ง ์๋ ํ Google์ ์ฒ์๋ถํฐ ํฌ๋กค๋ง์ ์๋ํ์ง ์์ต๋๋ค.
๋ค์ - ์ค์๋ก _์ ์ฐ๊ฒฐํ๋ ์ฌ๋์ ๋ฌธ์ ๊ฐ ์กด์ฌ www.mysite.com/people/_ ๋์ _์ www.mysite.com/people_ (- ์ฌ์ง์ด ๋๋ถ๋ถ์ ๊ฐ๋ฐ์ ์ฌ์ฉ์๋ฅผ์ํ ์ ํํ๊ฒ ์ผ์นํ๋ ๊ฒ ๋์).
์ด ๋ ๊ฐ์ง ์๋๋ฆฌ์ค ๋ชจ๋ SEO์ _์ํฅ์ ์ค ์ ์์ต๋๋ค_.
์ด์ SEO ์ํฅ์ ๊ณ ๋ คํ์ง ์๊ณ URL์ ์๋ฏธ๋ก ์ ์๋ฏธ๋ ์์ต๋๋ค. _does_ _ www.mysite.com/people / _์ด ๊ฐ๋ฆฌํค๋ ๊ฒ์ ๋ฌด์์
๋๊น? ์ด์์ ์ผ๋ก๋ ๋๋ ํ ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์๊ธฐ ๋๋ฌธ์ Next๋ pages > people > index.js
(_www.mysite.com/people_์ ๊ฒฝ์ฐ pages > people.js
์ ๋ฐ๋)์ ์๋ ๋ชจ๋ ๊ฒ์ ๋ฐํํด์ผ ํ์ง๋ง ๋์ ์๋ฌด ๊ฒ๋ ๋ฐํํ์ง ์์ต๋๋ค. ๋ผ์ฐํ
์ด ์๋ํ๋ ๋ฐฉ์์ ๋์ ์์ค์ ๊ฒฐํจ.
์ฃผ์ ๋ผ์ฐํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์ด๋ฏธ ์ด์ ๋ํ ์ผ๋ถ ์กฐํญ์ด ์์ต๋๋ค(์ isExact
React Router์ ๊ฒฝ์ฐ
๋๋ ๋น์ ์ด ์ด๋์์ ์๋์ง ์ดํดํ์ง๋ง, ์ด๊ฒ์ ์ฌ์ ํ โโ๋ถ๋ช์ณ์ผ ํ ๋๋ถ์ ๋ฌธ์ ๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ด๊ฒ์ 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://github.com/DevSpeak/next-trailingslash
?=์ ๊ด๋ จํ์ฌ ๋์ ๋ผ์ฐํธ๋ ํ์ด์ง์ ๋ํ ์ผ๋ถ ๊ฐ์ ๋ฐ ์ง์์ 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 ์ ์ _error.jsx
๊ฐ ์๋ ํ์ฐ์ค ๋ผ์ฐํ
๋ก์ง์ด ์๋๋ผ _errors_๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ฒ์์ ๊ณ ๋ คํ๋ฉด ์ ์๊ฐ์๋ ์ด ๋ชจ๋ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ด ํดํคํ๊ณ ๋งค์ฐ ์ ์ธ์ ์
๋๋ค. ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋ชจ๋ ์ฝ๋ ๊ธฐ๋ฐ์์ ์ด ์์
์ ์ํํ ๊ฒ์ผ๋ก ๊ธฐ๋ํ๋ ๊ฒ์ ํ์ ์ฌํญ์ด ์๋์ด์ผ ํฉ๋๋ค. ์ด๊ฒ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋์ด์ผ ํฉ๋๋ค. = ๋๋ ์ด ์กฐ๊ฑด์ด ๋ผ์ฐํ
๋ก์ง๊ณผ ํจ๊ป ๋ด์ฅ๋์ด์ผ ํ๊ณ React Router์ฒ๋ผ ์ตํธ์์ํ ์ ์๋ ์ต์
์ด ํ์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
@๋๋ค๋์ํ
์ฐจ๊ธฐ ์์ถ์ ๊ฒฝ์ฐ์๋ ์ด๋ ๋ถ๊ฐํผํ๋ค.
์ ๊น - ๋ฌธ์ ๋ฅผ ์ฝ๊ณ ํํ ์ฌ๋์ ์กฐ๊ฑด์ ์ฒ๋ฆฌํ๋ ํน์ ์ฝ๋๊ฐ ์๋ค๋ ๊ฒ์ ์ดํดํ์ต๋๋ค.
ํ์ด์ง๋ 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 ์ฌ๋ํฉ๋๋ค - ์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ์ ๋ฐ์ ๊ฐ์ ธ๋ค ์ค ๊ฒ์
๋๋ค. ์ฆ, ์ฌ์ฉ์ ์ ์ ์๋ฒ๋ฅผ ๋ง๋ค์ง ์๊ณ ๋ ๋ฆฌ๋๋ ์
์ ๋ํ ์ผ์ข
์ ์ง์์ด ์์ต๋๋ค. ์ด๊ฒ์ ์ฌ์ฉ์๊ฐ ์ถ๊ฐํ๋ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด next.config.js
์์ ๋ฆฌ๋๋ ์
์ ์ค์ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ฌ์ ํ ํ์์ ์
๋๋ค. ๋๋ ์ธ๊ธ๋ @bitjson ๊ณผ ๊ฐ์ ๋ชจ๋ ๊ฒฝ์ฐ๋ฅผ ํฌ์ฐฉํ๊ธฐ ์ํด ์ ๊ท์์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
.replace(/\/+$/, '')
.replace(/\/+#/, '#')
.replace(/\/+\?/, '?')
.replace(/\/+/g, '/')
๋ ๊ฒฝ์ฐ ๋ชจ๋ ํต์ฌ ํ์ด ์ด RFC์ ์ฐ์ ์์๋ฅผ ์ ํ๊ณ ์๋ค๋ฉด ํ ๋จ๊ณ ๋ ๋์๊ฐ ์ด๋ฅผ _์ ํ ํด์ _ํ ์ ์๋
// next.config.js
module.exports = {
ignoreStrictRoutes: false, // default value: true
};
๊ฒฐ๋ก ์ ์ผ๋ก ์ด๊ฒ์ ๋๋จํ ๋ฐ์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. @Timer!! ๐ฅ
@nik-john "/:path*/"
์์ ์ง์ ํ ๊ฒฝ๋ก๋ ๋ชจ๋ catchํด์ผ ํฉ๋๋ค( :path
๋ ๋จ์ผ ์ธ๊ทธ๋จผํธ๋ฅผ catchํ๊ณ *
๋ 0์์ n๊ฐ์ ์ธ์คํด์ค๋ฅผ catchํฉ๋๋ค.)
@Janpot ์ ๋ง์์ฌ ๐คฆโโ ๊ทธ ์ ๊ท์์์ ํํ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ ๊ณ ๋ คํด์ผ ํ ๊ฒ ๊ฐ์์
๋ํ ๋๋ ์ฌ์ ํ ๋ ๋ฒ์งธ ๋ถ๋ถ์ ์ง์งํฉ๋๋ค.
๋ ๊ฒฝ์ฐ ๋ชจ๋ ํต์ฌ ํ์ด ์ด 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์์ ์ด๊ฒ์ ์ป์๊ณ ์๋ฒฝํ๊ฒ ์๋ํ์ต๋๋ค. ์ด ์๋ฃจ์ ์ ์ต์คํ๋ ์ค์ ํจ๊ป ์๋ํฉ๋๋ค.
@GaneshKathar Express๋ฅผ ์ฌ์ฉํ์ง ์๋ Next.js๋ฅผ ๊ณ ๋ คํ๋ฉด ์ด๊ฒ์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
๋๋ ์ฐ๋ฆฌ๊ฐ ์ด๊ฒ์ ๋์ํ ์ ์์ผ๋ฉฐ ๊ตฌ์ฑํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋๋ ์ค์ ๋ก ํํ ์ฌ๋์๋ฅผ ํญ์ ์ํฉ๋๋ค. ์๋ 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
์ก์ธ์คํ๋ฉด location
๊ฐ /authors/
๊ฐ๋ฆฌํค๋ 302๊ฐ ์ ๊ณต๋ฉ๋๋ค. 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 };
}
๋ฌธ์ ๋ฅผ ๊ทน๋ณตํ๋ ์ข์ ๋ฐฉ๋ฒ์ ๋๊น?
์ด๊ฒ์ ์ด๋ค๊ฐ์?
ํ์ด์ง/_app.jsx
```'react'์์ React ๊ฐ์ ธ์ค๊ธฐ;
'next/app'์์ ์ฑ ๊ฐ์ ธ์ค๊ธฐ;
๋ด๋ณด๋ด๊ธฐ ๊ธฐ๋ณธ ํด๋์ค MyApp ํ์ฅ ์ฑ {
๋ ๋๋ง() {
const { ๊ตฌ์ฑ ์์, pageProps, ๋ผ์ฐํฐ: { 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 };
};
์, ๋ค๋ฅธ ๊ฒ์ด ๊ฒฐ์ ๋์ง ์๋ ํ @cansin ์ ํ ๊ฒ์ ๋๋ค :) ๊ฑด๋ฐฐ!
@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
๋งํฌ ์ํ์ ํํ ์ฌ๋์๋ฅผ ์ถ๊ฐํ ์ ์์ง๋ง ์ด ๊ฒฝ์ฐ ๋ฆฌ๋๋ ์
ํ์ง ์๋๋ก ํด์ผ ํฉ๋๋ค.
์ค๋ฅ ๊ตฌ์ฑ ์์์์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๊ตฌํํ ๋์ ๋ฌธ์ ๋ ๊ฐ๋ฐ ์ค์ ์๋ฆผ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ ๋๋ฅผ ๊ท์ฐฎ๊ฒ ํ๋ค๋ ๊ฒ์ ๋๋ค. ์ด์ ํด๋ผ์ด์ธํธ ์ธก ๋ฆฌ๋๋ ์ ์ ๋ํ ๋ช ๊ฐ์ง ๊ฐ์ ์ฌํญ:
๊ฐ์ ๋ ์ ์ ์ด์ ํด๋ผ์ด์ธํธ ์ธก์์ ๋ค์/๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ๊ณ URL ๊ต์ฒด๊ฐ ๋ค์ ๋ก๋ ์์ด ๋ฐ์ํ๋ค๋ ๊ฒ์ ๋๋ค.
ํ์ด์ง/_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 ๊ทธ ์ฌ๋์ ์๋์ง๋ง, ๋ถํํ๋ ์ด๋ฐ ์ผ์ด ๋ง์ด ๋ฐ์ํฉ๋๋ค. ์ ๋ ํ์ฌ WordPress ์ฌ์ดํธ๋ฅผ Next.js๋ก ์ ์ง์ ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๋ ๊ณผ์ ์ ์์ผ๋ฉฐ ์ด๋ค ์ด์ ๋ก ์๋ "๊ฐ๋ฐ์"๋ ๋ชจ๋ ๋จ์ผ URL์ ํํ ์ฌ๋์๋ฅผ ์ ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๊ธฐ ๋๋ฌธ์ ํ์ฌ ํํ ๋ ๊ฐ์ง ๋ชจ๋์ ๋ํ ์๋ง์ ์์ฒญ์ด ์์ต๋๋ค. ์ฌ๋์ AND ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์.
ํ์ค 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์ ๋ด
๋๋ค.
๋๋ ๊ทธ ๋ถ์พํ ํดํน์์ด ์ ํ ์ฌ๋์๊ฐ์๋ URL์ ์ผ์น์ํค๊ธฐ ์ํด next
๋ผ์ฐํฐ๋ฅผ ์ ํธํฉ๋๋ค.
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๋ฅผ /page/about/ ํด๋์ ๋ฃ์๊ธฐ ๋๋ฌธ์ /about/ ๋ฐ /about์ด ๋์ผํ๊ฒ ์๋ํ ๊ฒ์ผ๋ก ์์ํ์ต๋๋ค.
๋๋ 1990๋ ๋ ํ๋ฐ์ HTML FTP๋ก ๋ด ์๋ฒ์ ์น ์ฌ์ดํธ๋ฅผ ๋ง๋ค๊ธฐ ์์ํ๊ณ ๋์ค์ PHP์ Apache, ๊ทธ๋ฆฌ๊ณ ๊ฒฐ๊ตญ์๋ Java ์๋ฒ๋ก ์ฎ๊ฒผ์ต๋๋ค. ์ง๊ธ์ ๋ชจ๋ฐ์ผ ์ฑ์ ์ ๋ฌธ์ผ๋ก ํ๊ณ ์์ต๋๋ค. ์ด ๋์์ด ๊ธฐ๋ณธ๊ฐ์ด ์๋๋ฉฐ ๋ด dev ์๋ฒ์์ ์์ ํ๋ ค๋ฉด ์ฌ์ฉ์ ์ง์ ์๋ฒ ํ์ด์ง๋ฅผ ์์ฑํด์ผ ํ๋ค๋ ๊ฒ์ด ์ด์ํ๊ฒ ๋๊ปด์ง๋๋ค.
์ ์ ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์ํํ ๊ณํ์ด๋ฏ๋ก ์ฌ์ฉ์ ์ง์ ์๋ฒ๋ฅผ ์์ฑํ์ง ์์๋ ํ๋ก๋์ ์ ํ์๋์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ๋ฐ ๋ฐ ๋๋ฒ๊น ์ ์ฝ๊ฐ ๋ ์ฑ๊ฐ์๊ฒ ๋ง๋ญ๋๋ค.
๊ฒ์ผ๋ฅธ ๊ฐ๋ฐ์๊ฐ ๊ฐ๋ฐ/๋๋ฒ๊ทธ ์๊ฐ์ ์ํด ์ถ๊ฐ ๋ผ์ฐํ ๋ก์ง์ ์์ฑํ ํ์๊ฐ ์๋๋ก ์ด ๋ฌธ์ ๋ฅผ ์์ ํ๋ "next dev" ํ๋๊ทธ๋ฅผ ์ป์ ์ ์์ต๋๊น?
๊ฐ์ฌ ํด์!
ps: ์, /about
์ /about/
๋ ์์ ํ ๋ค๋ฅธ URL์ด๋ผ๋ ๊ฒ์ ์๊ณ ์์ต๋๋ค. /pages/about/
ํด๋ ์์ index.tsx
ํ์ผ์ ๋ฃ์์ ๋ ์ ๋ง ํผ๋์ค๋ฌ์์ /about
๊ฒฝ๋ก์์๋ง ์๋ํ์ง๋ง /about/
์์๋ ์๋ /about/
. ๋ฐ๋์๋ค๋ฉด ๋ ๋๋ผ์ง ์์์๊น.
์กฐ๋ฌ์ฒญ : ๋๋์ด ๋ ์ถ๊ฐ ํผ๋ํ๋ค <Link></Link>
๊ตฌ์ฑ ์์๋ฅผ ๊ทธ ํฌ์ธํธ /about/
์์๋๋ก ์๋ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๋ธ๋ผ์ฐ์ ์์ ์๋ก ๊ณ ์นจ์ ๋๋ฅด๋ฉด URL์ด ๋ณ๊ฒฝ๋์ง ์์๋๋ผ๋ ์ฆ์ 404์ด๊ฐ ๋ฉ๋๋ค. ๊ทธ๊ฒ์ ๋งค์ฐ ๋๋ผ์ด ์ผ์ด์์ต๋๋ค. :-NS
ํ์ง๋ง ์ ๊น, ์ํฉ์ด ์
ํ๋ฉ๋๋ค! ํํ ์ฌ๋์๋ฅผ ์ ๊ฑฐํ๊ณ ๋ฆฌ๋๋ ์
ํ๋ ์ฌ์ฉ์ ์ ์ checkForTrailingSlash
ํจ์๋ฅผ _error.js
๋ด๋ถ์ ์ถ๊ฐํ์ต๋๋ค. ์ด๊ฒ์ (๋ง์นจ๋ด) ์ฌ์ฉ์ ์ง์ 404 ํ์ด์ง๋ฅผ ์ถ๊ฐํ๊ณ Next.js๊ฐ ์ฌ์ฉ์ ์ง์ 404 ํ์ด์ง๋ฅผ ์ฌ์ฉํ์ฌ 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 ์์ด ๋์ผํ ํ์ด์ง๋ฅผ ๋ฐํํฉ๋๋ค.
๊ทธ๋ฌ๋ ํ ๊ฐ์ง ๊ฒฐํจ์ด ์์ต๋๋ค. ๋์ ๊ฒฝ๋ก์ getStaticPaths๋ฅผ ์ฌ์ฉํ๋ ํ์ด์ง๊ฐ ๊ฑฐ์ ์์ต๋๋ค. getServerSideProps๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ์ด๋ฌํ ๋์ ๊ฒฝ๋ก๋ฅผ ํํ ์ฌ๋์๋ก ํ์ํ๋ฉด ๋จผ์ 404๋ฅผ ๋ฐํํ ๋ค์ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
๋ฉ๋๋ค. .
/api/test ํด๋๋ก ์์ ์ค์ ๋๋ค.
๊ทธ๊ฒ์ ์๋
๊ทธ๋ฆฌ๊ณ ๋๋ ์ด๊ฒ์ด ์๋ํ์ง ์๋๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค.
์ด๊ฒ์ด ๊ด๋ จ ๋ฌธ์ ์ธ์ง ํ์คํ์ง ์์
P/D exportTrailingSlash = true๋ก ํด๊ฒฐ๋์ง ์์
์ด๊ฒ์ ๋งค์ฐ ์ค๋๋ ๋ฌธ์ ์ ๋๋ค. ๊ทธ๋ ๊ฒ ์ค๋ซ๋์ ํด๊ฒฐ๋์ง ์๋ ์ด์ ๊ฐ ์์ต๋๊น?
๋ฌด์์ด ๋ ์ด์ ์๋ํ์ง ์๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
๋ด ๋ง์ ์๊ตฌ ์ฌํญ์ด ๋ค์๊ณผ ๊ฐ๋ค๋ ๊ฒ์ ๋๋ค.
| | exportTrailingSlash: ๊ฑฐ์ง | exportTrailingSlash: ์ฐธ |
|-------------------------|-------------------------- -----|------------------------------|
| 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)
๋งฅ / ๋ฆฌ๋
์ค์ - ๋ณผ ์ด ์ฐฝ๋ฌธ์)
@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
๊ฐ ๊ฐ๋ฐ ๋ชจ๋์ ๊ด๋ จ์ด ์๋ค๋ ๊ฒ์
๋๋ค.
ํผ๋์ ์์ธ ์ค ํ๋๋ exportTrailingSlash
๊ฐ ์์ ๋ next.js๊ฐ ๋งํฌ์ ์ฌ๋์๋ฅผ ์ถ๊ฐํ๋ค๋ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ๊ฐ๋ฐ์์๋ ๋ฐ์ํฉ๋๋ค. ์ด๋ ๊ฒ ํด์ผ ํ๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด์จ๋ ์ด๊ฒ์ example/index.html
๋ example.html
์ ๊ดํ ๋ฌธ์ ์ผ ๋ฟ๋ง ์๋๋ผ ์์ ํด์ผ ํ๋ ๋งํฌ๋ ํ์ํฉ๋๋ค.
URL์ด ๊ฐ๋ฐ์์๋ ์๋ํ์ง ์์ง๋ง ํ๋ก๋์ ์์๋ ์๋ํ๋ค๋ฉด ์ต์ ๋๋ผ์์ ์์น์ ์ด๊ธ๋๋ค๊ณ ์๊ฐํ์ง ์์ต๋๊น? ๋๋ ์ด๊ฒ์ด ์ฌ์ ํ ๋ฒ๊ทธ๋ก ๊ฐ์ฃผ๋์ด์ผํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋ด๊ฐ ํ๋ฆด ์๋ ์์ง๋ง exportTrailingSlash ์ต์
์ URL์ด /something
๋ /something.html
๋ฅผ ์ ๊ณตํ๋๋ก ๊ตฌ์ฑ๋์ง ์์ nginx ์๋ฒ์ฉ์ด์์ต๋๋ค.
์ด๊ฒ์ ๋ก์ปฌ ๊ฐ๋ฐ์ ์ฌ์ฉ๋๋ ๋ค์ ์๋ฒ์ ๊ฒฝ์ฐ๊ฐ ์๋๋๋ค. ๋ฐ๋ผ์ ์๋ํ๋ ๊ฒ๊ณผ ์๋ํ์ง ์๋ ๊ฒ์ ์ฑ์ ์ ๊ณตํ๋ ํญ๋ชฉ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค.
๋น์ ์ ๋ ๊ฒฝ์ฐ ์ 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 ์์ด ๋์ผํ ํ์ด์ง๋ฅผ ๋ฐํํฉ๋๋ค.
๊ทธ๋ฌ๋ ํ ๊ฐ์ง ๊ฒฐํจ์ด ์์ต๋๋ค. ๋์ ๊ฒฝ๋ก์ getStaticPaths๋ฅผ ์ฌ์ฉํ๋ ํ์ด์ง๊ฐ ๊ฑฐ์ ์์ต๋๋ค. getServerSideProps๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ์ด๋ฌํ ๋์ ๊ฒฝ๋ก๋ฅผ ํํ ์ฌ๋์๋ก ํ์ํ๋ฉด ๋จผ์ 404๋ฅผ ๋ฐํํ ๋ค์ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ๋ฉ๋๋ค. .
@gauravkrp @AlexSapoznikov ์๋ฃจ์ ์ ์ค์ ๋ก ์ฌ์ ํ ํ์ด์ง์ ๋ํ 404๋ฅผ Google์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ ์ค์ ๋ก ๋งค์ฐ ์ค์ํ ์ถ๊ฐ ์ฌํญ์ ๋๋ค(๋ฆฌ๋๋ ์ ์ด ํด๋ผ์ด์ธํธ์์ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์). SEO๊ฐ ์ฐ๋ฆฌ ์ค ๋ง์ ์ฌ๋๋ค์ด ์ฒ์์ Next.js๋ฅผ ์ฌ์ฉํ๋ ์ฃผ์ ์ด์ ๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๋๋ ๋ํ ์ด๊ฒ์ 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
๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
ํ๋ก๋์ ์์ ์ด ๋ฌธ์ ์ ๋ํ ์์ ํด๊ฒฐ์ฑ ์ด ์์ต๋๊น?
์ฐ๋ฆฌ๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ธฐ๋ฅ์ ๊ณง ์ถ์ํ ์์ ์ ๋๋ค.
ํ๋ก๋์ ์์ ์ด ๋ฌธ์ ์ ๋ํ ์์ ํด๊ฒฐ์ฑ ์ด ์์ต๋๊น?
์ด ์ค๋ ๋์๋ ๋ง์ ๊ฒ์ด ์์ง๋ง ํ์ฌ @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
}
}
๊ธฐ๋ฅ์ด ์นด๋๋ฆฌ์์์ ๋ง์คํฐ๋ก ์ด๋ํ๋ ๋ฐ ์ผ๋ง๋ ๊ฑธ๋ฆฝ๋๊น?
์ค๋น๊ฐ ๋๋ฉด ์ฒ๋ฆฌ ์ค์ธ ์ฒ๋ฆฌ์ ์ฌ์ ํ ์ฃ์ง ์ผ์ด์ค๊ฐ ์์ต๋๋ค. ์ผ๋จ ๊ทธ๊ฒ๋ค์ด ์์ ๋๋ฉด ์์ ์ ์ผ๋ก ๊ฐ ์ ์์ต๋๋ค.
@imneutkens @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์์ ํ
์คํธํ๋๋ฐ ์์์ ์ค๋ช
ํ ๊ฒ๊ณผ ๋์ผํ ๋์์ ํฉ๋๋ค.
๋๋ ๋ ๊ฒฝ์ฐ์ ํ๋์ด ๊ฐ์์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋ฆฌ๋๋ ์ ์ ์ด์ ๋ ๊ฒ์ ์์ง์ ์ค๋ณต ์ฝํ ์ธ ๊ฐ ํ์๋์ง ์๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค. ์ ํํ ์ฌ์ฉ ์ฌ๋ก๋ ๋ฌด์์ ๋๊น?
์์ง ์์ ์ ์ธ ๋ฆด๋ฆฌ์ค๋ก ๋ณํฉ๋์ง ์์ ๊ฒฝ์ฐ ํ์๋ ๋ฌธ์ ์ธ ์ด์ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ๋ด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ดํดํ๋ค๋ฉด ์ง๊ธ์ ์นด๋๋ฆฌ์ ๋ฆด๋ฆฌ์ค์์๋ง ์์ ๋์์ต๋๋ค. ๋ง์ต๋๊น?
๋ฌธ์ ๋ ์นด๋๋ฆฌ์์์ ์ฆ์ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ๊ด๋ จ pull ์์ฒญ์ด ๋์ฐฉํ๋ฉด ๋ซํ๋๋ค. ์ด ๊ธฐ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ ์นด๋๋ฆฌ์ ์ฑ๋๋ก ์ ๊ทธ๋ ์ด๋ํ์ญ์์ค.
์ ๋ค๋ฆฐ๋ค. @Timer๋, ๊ฐ์ฌํฉ๋๋ค!
@Janpot https://github.com/issues/
๋ฐ https://github.com/issues
๋ฆฌ๋๋ ์
์์ด ๋์ผํ ๋์์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
https://twitter.com/explore/
๋ฐ https://twitter.com/explore
, ์ด๊ฒ๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค.
๊ฒ์ ์์ง์ ๋ฌธ์ ๊ฐ ์์ผ๋ฉด Github์ Twitter์์ ํด๊ฒฐํ์ง ์์ ์ด์ ๋ ๋ฌด์์
๋๊น?
๋๋ ๊ทธ๊ฒ์ด ๋ชจ๋ ์น ์ฌ์ดํธ์ ๊ธฐ๋ณธ ๋์์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
ํน๋ณํ ์ฌ์ฉ ์ฌ๋ก๋ ์์ผ๋ฉฐ ๊ทธ๋ ๊ฒ ์๋ํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๊ฒ์ ์์ง์ ๋ฌธ์ ๊ฐ ์์ผ๋ฉด Github์ Twitter์์ ํด๊ฒฐํ์ง ์์ ์ด์ ๋ ๋ฌด์์ ๋๊น?
@armspkt ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์ฌ๋ฌ ๊ฐ์ง <link rel="canonical">
์์ฑ์ ์ฌ์ฉํ์ฌ ๊ฒ์ ๋ด์๊ฒ ํฌ๋กค๋งํด์ผ ํ๋ ํ์ด์ง์ ๋ค๋ฅธ ๋ฒ์ ์ด ์ค๋ณต๋ ๊ฒ์ผ๋ก ํ์ํด์ผ ํ ํ์ด์ง๋ฅผ ์๋ ค์ค๋๋ค.
๋ฐ๋ผ์ ๋ฆฌ๋๋ ์ ์ ์น์ฌ์ดํธ์์ 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
๋ํด ์๋ํ์ง ์๋ ๊ฒ /page/
๋ฅผ /page
(๋๋ ๊ทธ ๋ฐ๋๋ก) ๋ฆฌ๋๋ ์
ํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ๋ฌด์์
๋๊น?
trailingSlash
์ต์ ์ดnext export
๋ํด ์๋ํ์ง ์๋ ๊ฒ/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 ์ค๋ฅ๋ก ์ด์ด์ง๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ฐ๋ฆฌ๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ธฐ๋ฅ์ ๊ณง ์ถ์ํ ์์ ์ ๋๋ค.