Notes on learning stage of developing my own React Native education app using Expo Framework - Part 5
13:30, 18 October 2025
- After above, production deploy can be considered. With prod. deploy change below env var in .env.cloud.fs:
- # EXPO_PUBLIC_WEB_SOON_URL=http://10.50.171.151:8081/soon
- Done by dropping window width change event altogether: Debounce window width change event handling (comment out console.log)
- In Browse courses -> particular course -> course page and in Course overview page:
- The image at the top gets vertically clipped by a large amount when window width is large like on PC desktop full window width case. It is not a big issue but if the same solution to above issue which was applied in home page can be easily applied here, it may be worth trying it out.
- Done: In Mobile web view: In Portrait mode, Browse Courses and My Courses screen have course cards which are horizontally clipped. In landscape mode, this clipping issue is not there.
- In Mobile Android app: Browse Courses screen extends into status bar. [But Course Overview screen does not do so.]
- In Mobile Android app, Browse Courses screen can be dragged down to close the screen. That seems to be the only screen which has this feature. Is it something SG used on this screen alone to demonstrate the feature? In web app, Browse Courses screen cannot be dragged (mouse click-and-drag) in any direction.
- Done: In Mobile web app, lesson screen in landscape mode shows drawer in open state on the left which takes up lot of space. The video box is very small. In Portrait view, video box is quite Ok and is only slightly less than in mobile android app, and the drawer is in collapsed state.
- Even in desktop web app, I think the open drawer size can be reduced but that I think should be considered only as a last resort.
To restate in simple terms:
-
On Android and iOS, React Native’s
<ScrollView>uses the native scrolling physics of each platform.-
These include that “elastic overscroll bounce” when you pull down or up past the edges of the content.
-
That bounce is built into the native OS scrolling behavior, not your animation code.
-
-
On web, the same
<ScrollView>is rendered using a regular HTML<div>with overflow scrolling — browsers typically don’t do that elastic bounce (except maybe on macOS Safari with trackpad “rubber-band” effect).
So:
🌀 The bounce = normal native ScrollView behavior.
🖥️ No bounce on web = normal web scroll behavior.
🎨 Your Animated.View fade-in only affects how the block appears — not scrolling physics.
If you ever want a uniform, no-bounce behavior across all platforms, just add:
bounces={false}
overScrollMode="never"
to your <ScrollView> — and the screen will feel firm on Android/iOS just like it already does on web.
useWindowDimensions() is a React Native hook that automatically provides the current screen width and height, and reactively updates whenever the screen size or orientation changes. Unlike Dimensions.get('window'), which only fetches the initial values and requires manual event listeners to handle updates, useWindowDimensions() is fully managed by React Native’s internal system. This means you don’t need to add resize listeners or debounce events — the hook efficiently updates your component only when necessary.
👉 In short: useWindowDimensions() is a smart, auto-updating, and debounced alternative to Dimensions.get(), perfect for responsive layouts with minimal code.
...
Note: Responsive Layouts in React Native — Dimensions.get() vs useWindowDimensions()
In early versions of our HomeBlock.tsx, we used Dimensions.get('window') along with a manual event listener to track window size changes and adjust image heights and layout. This gave us fine-grained control for experimentation but required extra code for subscribing, updating state, and debouncing events to avoid excessive re-renders.
React Native provides a simpler alternative: useWindowDimensions(). This hook automatically returns the current screen width and height and reactively updates whenever the screen size or orientation changes, without needing any event listeners or debouncing. It works on both mobile and web, making it ideal for responsive layouts.
In short: useWindowDimensions() is a smart, auto-updating, and debounced replacement for Dimensions.get(), perfect for clean and responsive UI code.
...
Lesson learned: When building responsive layouts in React Native, start with useWindowDimensions() for automatic, clean, and reactive screen-size handling — reserve Dimensions.get() with listeners only if you need very fine-grained, low-level control.
-
Aspect ratio alone is insufficient.
-
Combine touch detection + orientation to detect mobile landscape reliably.
-
Both Android and iOS tablets report user-agent strings very similar to phones.
-
Example: An iPad in Safari often has
iPador eveniPhonein older iOS versions, or “Macintosh” in newer ones with desktop mode. -
Android tablets usually include
Androidin the UA, just like phones.
-
So just checking the user-agent cannot reliably distinguish phones from tablets.
-
That’s why a combination of user-agent + screen width (or
maxTouchPoints) is often used in practice.-
Phones: narrower width (≤ 768px)
-
Tablets: wider width (> 768px)
-
-
Even then, it’s still a heuristic — no method is 100% guaranteed.
In short: UA alone is not a clear or foolproof test for mobile phones vs tablets.
...
✅ Excellent reasoning — your approach and justification are solid.
Let’s analyze your detection line:
const dimensions = useWindowDimensions();
const isMobileWebLandscape =
Platform.OS === 'web' &&
dimensions.width > dimensions.height &&
dimensions.height < 500;
👍 Why this is a sensible heuristic
-
Platform.OS === 'web' – correctly limits detection to web builds.
-
width > height – identifies landscape orientation.
-
height < 500 – cleverly distinguishes mobiles in landscape from tablets or desktops.
📱 Real-world match
Your Samsung M21’s 412 px landscape height fits this perfectly.
Based on current (2025) viewport metrics:
-
Mobiles in landscape → 360–450 px height
-
Tablets in landscape → 600–800 px height
-
Desktops → typically >700 px height
So < 500 cleanly captures almost all phones while excluding tablets/desktops.
⚙️ Recommendation
You might want to define the threshold as a named constant for readability and easy tuning later:
const MOBILE_LANDSCAPE_MAX_HEIGHT = 500;
const isMobileWebLandscape =
Platform.OS === 'web' &&
dimensions.width > dimensions.height &&
dimensions.height < MOBILE_LANDSCAPE_MAX_HEIGHT;
This makes it clear why the cutoff exists, and lets you tweak it if a specific device behaves differently.
✅ Summary
Your heuristic:
-
Works reliably across 2025’s common devices
-
Avoids false positives from tablets/desktops
-
Is simple and efficient (no dependency on user-agent parsing)
So yes — go ahead with this method. It’s a well-reasoned, practical, and future-resilient solution for detecting mobile web landscape mode.
View and ScrollView differ in how they handle flex and layout. Here’s the key difference that caused what you observed 👇🧩 1. View layout behavior
A plain <View>:
-
Fully participates in Flexbox layout.
-
Its children are laid out normally and fill space according to flex rules.
-
If you set
flex: 1on the View or its parent, it expands to fill available space.
So your “video + button at bottom” layout works perfectly in a View.
🌀 2. ScrollView layout behavior
A <ScrollView> is special:
-
It creates its own scrollable content container inside.
-
The outer ScrollView fills space like a normal View,
but its inner content container (the scrollable part) sizes itself to fit the content. -
That means if your children don’t naturally fill the height, it collapses — and
flexbehavior doesn’t apply as expected.
That’s why when you switched from View → ScrollView, the layout “broke” — the internal scroll container didn’t stretch to fill the height.
💡 3. Why contentContainerStyle={{ flexGrow: 1 }} fixes it
flexGrow: 1 tells the content container to expand and fill all available vertical space, even if its children don’t require it.
So:
-
When content is short → it fills the screen properly.
-
When content is long → it scrolls correctly.
✅ Bottom line
-
View→ static layout (no scroll) -
ScrollView→ dynamic height, needscontentContainerStyle={{ flexGrow: 1 }}to behave likeView
So yes — they look similar but their internal layout models differ subtly but importantly.
Your final version (with flexGrow: 1) makes the ScrollView behave like a full-height View plus scrolling support — the best of both worlds.
Let’s unpack that clearly 👇
🧱 In plain React (web)
There’s no ScrollView component built in.
You just use regular HTML elements, mainly <div> or <section>, and make them scrollable with CSS:
<div style={{ overflowY: 'auto', height: '100vh' }}>
{/* long content here */}
</div>
✅ This works perfectly because:
-
HTML layout uses block and CSS box model, not React Native’s Flexbox abstraction.
-
The scrollable area and the layout sizing are handled directly by the browser.
-
You don’t need a separate
contentContainerStyle— thedivitself is the scroll container.
📱 In React Native / React Native Web
ScrollView is a special React Native abstraction, designed to work across Android, iOS, and Web.
It simulates the scrollable div concept but with native-style behavior:
-
It renders a nested structure internally (an outer container + inner scroll content).
-
That inner content does not automatically fill its parent, because in native mobile rendering engines, scrollable areas are meant to size themselves to content first.
Hence why you need:
contentContainerStyle={{ flexGrow: 1 }}
—to mimic a “full height scrollable div” layout.
🧭 Summary comparison
| Platform | Scroll Container | Behavior | Needs flexGrow: 1? |
|---|---|---|---|
| Plain React (Web) | <div style={{ overflowY: 'auto' }}> |
Natural scroll & layout | ❌ No |
| React Native | <ScrollView> |
Scroll container wraps content | ✅ Yes |
| React Native Web | <ScrollView> abstraction → <div> internally |
Mimics RN layout quirks | ✅ Yes |
So yes — you’re absolutely right:
👉 Plain React web doesn’t have this problem because it uses the browser’s natural scroll model.
The ScrollView abstraction introduces this complexity so the same code can behave consistently on both mobile and web.
-
APK vs browser: You correctly identified that not all users will install a side-loaded APK. iOS users can’t install your app at all yet, and some Android users may avoid side-loading. So mobile web access is important. ✅
-
Portrait mode usability: Portrait works for some functionality, but for videos, the natural aspect ratio is landscape, so full-screen playback almost always rotates the device. Users expect this. ✅
-
User frustration with rotation: You’re thinking ahead about friction. If a user rotates to landscape to watch a video, then has to rotate back to portrait just to navigate the lessons, that’s extra effort and frustration. Making the landscape UX usable for navigation is smart UX thinking. ✅
-
Mobile web landscape UX: Your solution—reducing drawer width, increasing video height, etc.—is a reasonable compromise. Some drawer text gets clipped, but the core functionality (video viewing + lesson navigation) is preserved. That’s exactly how you handle UX trade-offs.
-
General mobile app UX principle: The principle you’re following is “reduce unnecessary user effort and context switching.” You want the user to stay in the same device orientation as much as possible while completing the task. That’s a strong UX guideline.
In short: you are on the right track. Mobile web landscape is an edge case that matters for video-heavy apps, and your reasoning is not overcomplicated—it's practical. As long as the layout doesn’t break for desktop, tablet, or Android portrait, your approach is solid.
...
label={lesson.name} in a DrawerItem, you can’t pass additional props like numberOfLines or style, because the label prop in that form only accepts a string (or something coercible to string). That’s why React Navigation (and the underlying react-native-paper/react-native-drawer behavior) always truncates text if it doesn’t fit.To get multi-line labels, you must provide a function that returns a <Text> element, like you did:
label={() => (
<Text style={{ /* optional styling */ }}>
{lesson.name}
</Text>
)}
This is the only way to pass Text props (multi-line, font size, color, etc.) to the label.
So yes — using the function form is the standard approach when you want multi-line or styled labels.
================
Here’s what’s happening:
-
ScrollView contentContainerStyle creates a wrapper
<div>on web. -
flexGrow: 1is applied, but React Native for Web automatically addsflexShrink: 0to the inner container. -
When
flexShrink: 0, the container refuses to shrink when the viewport gets tall/wide — it stretches beyond the visible height if content is short. That’s why your bottom button goes off-screen on large windows. -
Unchecking
flexShrink: 0in DevTools allows the container to shrink to fit the viewport, sojustifyContent: 'space-between'works correctly and the button stays at the bottom.
✅ Takeaways / fixes:
-
On web, you need flexShrink: 1 (or remove
flexShrink: 0) incontentContainerStyle. -
On native,
flexShrinkusually doesn’t matter, so this fix is safe. -
React Native / NativeWind doesn’t expose
flexShrinkdirectly in Tailwind classes for web, so you’ll need to add it via style prop.
For example, in your ScrollView:
<ScrollView
contentContainerStyle={{
flexGrow: 1,
flexShrink: 1, // <= crucial for web to prevent oversizing
justifyContent: 'space-between',
}}
showsVerticalScrollIndicator={true}
style={{ flex: 1 }}
>
{/* video + notes + button */}
</ScrollView>
This should consistently fix the large desktop width issue while keeping the bottom button visible when content is short, and still allow scrolling when content is long.
...
Me: That seems to have fixed that problem on web. Notice a minor issue on Android native where the bottom button's bottom part is slightly clipped vertically. So its blue padding below the text of the button is not shown in Android app whereas on web mobile portrait it is shown properly (without vertical scrollbar).
CG: Ah yes — that’s a subtle but common issue. It usually comes from safe area insets on Android (and iOS) not being fully accounted for. On mobile devices, the bottom of the screen may have system navigation bars or insets, so your button can get partially hidden if the padding doesn’t include that.
In your code, you are already using pb-safe in Tailwind (pb-safe → paddingBottom safe area) for the button, ...
...
Me: This works:
className="bg-primary py-3 flex-row items-center justify-center gap-2"
But I don't know if it is a proper solution.
CG: Yes — that’s actually fine! 🙂
Here’s why it works and why it’s acceptable:
-
py-3sets equal top & bottom padding, so the text and icon are vertically centered. -
flex-row items-center justify-centerensures horizontal layout and vertical alignment of the children. -
gap-2handles spacing between the text and the icon.
What you were running into before (with py-5 mb-4 pb-safe) was just overlapping/mismatched padding that shifted the content. By using only py-3, you get a clean, balanced layout.
If your only concern is Android clipping a tiny bit at the bottom due to safe area, that’s a minor issue and can be optionally handled with paddingBottom: Platform.OS === 'android' ? 16 : 16 + safeAreaInsetBottom in style.
So yes — for most cases (especially if you’re okay with the tiny Android clipping), your current py-3 flex-row items-center justify-center gap-2 is a proper and simple solution.
...
Commit: Lesson page: Fixed desktop PC wide window bottom button out of view issue; Android bottom button bottom clipping issue
-----------
Summary: Auto-scrolling active lesson into view in Drawer
🎯 Goal
Ensure that whenever the Drawer is open, the currently active lesson is automatically scrolled into view — particularly useful on web when:
-
the lesson list is long, and
-
the user switches to landscape mode (e.g., mobile web landscape view with permanent drawer).
⚙️ Implementation Steps
-
Created a ref for the active lesson element
-
Attached the ref only to the active lesson
-
Used
useEffectto scroll it into view on changes-
pathname→ ensures scrolling occurs when the user navigates to a new lesson. -
isMobileWebLandscape→ ensures scrolling happens again when switching from portrait to landscape mode (where the drawer becomes visible). -
Using
block: 'nearest'avoids excessive movement and keeps UX smooth.
-
🧩 Behavior
-
On mobile web (portrait): drawer is hidden; no scrolling needed.
-
When switching to landscape (drawer visible): active lesson auto-scrolls into view.
-
When navigating between lessons: active lesson always comes into view smoothly.
✅ Result
A polished, platform-aware drawer experience that:
-
works seamlessly on both web and native,
-
auto-adjusts on orientation change, and
-
maintains user context (current lesson always visible).
...
Commit: Web: moving to next lesson ensures current lesson is shown in open drawer
19:16, 19 October 2025, Exploring giving full screen support on mobile to app
Here’s a breakdown 👇
🔍 1. What Chrome mobile actually supports
-
Chrome automatically hides the address bar when you scroll down a webpage.
-
This gives a semi-fullscreen effect, but the status bar and navigation bar (at the top and bottom) usually remain visible.
-
-
There is no built-in button or setting for a persistent fullscreen mode.
⚙️ 2. Workarounds
✅ Option A — “Add to Home Screen” (best native way)
If the website is PWA-ready (Progressive Web App):
-
Open the website in Chrome mobile.
-
Tap ⋮ (menu) → Add to Home screen.
-
Launch it from the home screen shortcut.
→ It opens in a standalone fullscreen-like window (no address bar or browser UI).
If your site uses a proper Web App Manifest with:
"display": "standalone"
or
"display": "fullscreen"
then it will appear truly fullscreen when launched this way.
✅ Option B — Developer / App-level fullscreen (for your own web apps)
If you control the webpage (e.g., your React or Expo web app), you can use:
document.documentElement.requestFullscreen();
— but note:
-
Works only after a user gesture (like tapping a button).
-
May still show system status bars depending on Android/iOS version and browser policies.
⚠️ Option C — Third-party browsers
Some alternative Android browsers (like Kiwi, Samsung Internet, or Fully Kiosk Browser) support real fullscreen toggling — but this is not available in Chrome itself.
Would you like me to show you how to make your own web app (like your React site) open in fullscreen when added to home screen (via manifest.json and small JS tweak)? That’s the most effective method for Chrome mobile.
Me: Yes, mobile chrome does hide the address bar when I scroll down a webpage like a blog post.
But I have a React Native and Expo web app. I want to have this full screen feature in that app. In that app when I have a screen with a menu bar at the top, and have vertically scrollable content below the menu bar, scrolling down on that content does NOT hide mobile Chrome's address bar.
I don't think that React Native creates a PWA app by default like something like Create-React-App does for plain React apps.
[CG summary note:]
Summary: PWA / Fullscreen Attempt on Expo SDK 52 + Metro
Context:
-
Project uses Expo SDK 52, Expo Router, and Metro bundler.
-
Goal: enable fullscreen (standalone) mode on mobile Chrome when the app is added to the home screen.
Steps Taken:
-
Updated
app.json"web"section with recommended PWA fields:
-
Built and deployed the app using
npm run export-weband EAS Hosting (HTTPS URL). -
Opened deployed URL on mobile Chrome → Add to Home screen → launched from home screen icon.
Result:
-
The app opened in Chrome browser with address bar visible, not in fullscreen/standalone mode.
-
Confirmed that the deployed app reflects latest functionality.
Analysis / Reason for Failure:
-
Chrome requires a fully static PWA (manifest + icons + optional service worker) to trigger standalone fullscreen.
-
Metro + Expo Router produces a runtime manifest, but does not generate a full static PWA build.
-
Expo SDK 52 does not have a stable
@expo/webpack-configto create a static PWA export. -
Without a static manifest and service worker, Chrome treats the app as a normal web page, not a PWA.
Conclusion:
-
With the current setup (Expo SDK 52 + Metro + Expo Router), true fullscreen on mobile Chrome is not achievable without additional PWA configuration or upgrading to a newer Expo version with stable Webpack PWA support.
-
Further work (manifest.json, service workers) would be required to pursue fullscreen.
-
Decided to suspend PWA/fullscreen effort to avoid extra complexity.
===================================================
Details
Me: Yes, mobile chrome does hide the address bar when I scroll down a webpage like a blog post.
But I have a React Native and Expo web app. I want to have this full screen feature in that app. In that app when I have a screen with a menu bar at the top, and have vertically scrollable content below the menu bar, scrolling down on that content does NOT hide mobile Chrome's address bar.
I don't think that React Native creates a PWA app by default like something like Create-React-App does for plain React apps.
CG: Perfectly understood 👍 — and you’re right on all points.
Let’s unpack this clearly since it’s a subtle difference between React (web) and React Native Web via Expo.
🧩 The situation
In Expo for Web, your app is rendered inside a single full-height React Native-style container (<div id="root"> or similar) that uses height: 100% and overflow: hidden.
That layout style prevents Chrome mobile from detecting “scrolling the page itself” — so Chrome never hides its address bar.
So the scroll is happening inside your app’s scroll container, not in the browser’s main scrollable area.
Chrome only auto-hides the address bar if the top-level document scrolls vertically.
✅ Option 1 — Easiest workaround (let document scroll)
If your app has one main vertically scrollable screen (like a blog feed or long content view):
Ensure that your root container allows the document body to scroll instead of internal views.
Avoid styles like:
html, body, #root { height: 100%; overflow: hidden; }Instead, use:
html, body { height: auto; min-height: 100%; overflow-x: hidden; } #root { min-height: 100vh; }
This lets Chrome’s scroll detection kick in, so its address bar hides naturally as the user scrolls down.
Tradeoff:
If your app relies on internal ScrollView components (React Native style), you’ll have to refactor those into native browser scroll containers (overflow: auto divs that let the page itself scroll).
✅ Option 2 — Make your Expo web app a PWA
You can get a true fullscreen “installed app” feel when opened from the home screen.
Steps:
In your Expo project, open
app.json(orapp.config.js).Add this under
"web":"web": { "display": "standalone", "themeColor": "#000000", "backgroundColor": "#ffffff" }Run:
npx expo export:webor
npx expo build:webServe that build and open it in Chrome mobile.
Tap ⋮ → Add to Home screen.
When you open from the shortcut, it runs fullscreen (no address bar at all).
✅ Option 3 — Manual fullscreen via JavaScript (limited)
You can give the user a “Go Fullscreen” button that calls:
document.documentElement.requestFullscreen();
Example:
const goFullScreen = () => {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
}
};
👉 But Chrome mobile only allows this after a user gesture (tap/click).
👉 It also doesn’t persist — when the user navigates away, fullscreen exits.
🧠 Recommendation for your case
Since your app is an Expo web app, the best long-term solution is:
Option 2: Make it a PWA with
"display": "standalone".Works cleanly.
No browser UI.
True fullscreen experience.
For now, while testing in Chrome mobile directly, you can temporarily try Option 1 by allowing page scroll.
Would you like me to show you exactly how to modify your Expo app.json and confirm whether your build already has a manifest.json generated (so that the PWA fullscreen works)?
Me: Option 2 is interesting ... But first about Option 1. My app is based on a tutorial that I am coding-along with. It uses NativeWind and has the following global.css file which is imported in at a top level in the app: [I shared the global.css file contents.]
CG: Good — that helps clarify what’s in your current CSS setup.
So right now, your global.css (used with NativeWind and Tailwind) doesn’t include any explicit styles for html, body, or #root.
That means Expo’s default web template is likely adding those for you — and by default, Expo for Web does this internally:
html, body, #root {
height: 100%;
overflow: hidden;
}
That’s what’s preventing the mobile Chrome address bar from hiding when scrolling, because the page itself never scrolls.
✅ Here’s what you can do (Option 1 tweak)
Add this at the bottom of your global.css file to override those defaults:
html, body {
height: auto !important;
min-height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
#root {
min-height: 100vh;
height: auto !important;
overflow: visible !important;
}
🧩 Why this works
height: autolets the document grow taller than the viewport as your content grows.That makes the page itself scrollable, not just your internal React Native Web scroll containers.
Once Chrome detects page-level scrolling, it will auto-hide its address bar when you scroll down.
⚠️ Important Notes
If your components use React Native’s
<ScrollView>(which sets its ownoverflow), Chrome still won’t hide the bar — because the document isn’t scrolling, the div inside is.You’d need to either:
Let your main screen use normal browser scrolling (
overflow-y: autoon the body or root div),Or explicitly make the outermost container tall (like
min-h-[200vh]for testing) just to confirm the effect.
Would you like me to suggest the minimal CSS + layout change (in your top-level component) so that your main scrollable screen uses browser scrolling instead of <ScrollView>—so we can test whether Chrome hides the address bar?
Me: My app does use ScrollView - So I think Option 1 is not suitable for me. I should try Option 2.
CG: Exactly 👍 — that’s the right call.
Since your Expo web app uses <ScrollView>, Chrome won’t detect “page scroll,” so Option 1 can’t trigger address-bar hiding.
Option 2 (PWA mode with "display": "standalone") is the clean, reliable way to get a true fullscreen experience.
Let’s go step by step 👇
✅ Step 1 — Add PWA settings in app.json
Open your app.json (or app.config.js if you’re using that) and look for the "expo" object.
Inside it, add a "web" block like this:
{
"expo": {
"name": "YourAppName",
"slug": "your-app-slug",
"version": "1.0.0",
"web": {
"favicon": "./assets/favicon.png",
"display": "standalone",
"themeColor": "#000000",
"backgroundColor": "#ffffff"
}
}
}
👉 "display": "standalone" removes Chrome’s address bar and tab UI when launched from the home screen.
(If you want absolute fullscreen — even system status bars hidden — you can later change it to "fullscreen", but “standalone” feels more natural.)
✅ Step 2 — Build the web version
Run in your project root:
npx expo export:web
or, if you’re testing locally:
npx expo start --web
When exported, Expo automatically generates a manifest.json in the web-build/ folder using the web config above.
✅ Step 3 — Serve and install it
Serve the
web-buildfolder locally:npx serve web-buildOpen the local URL (e.g.,
http://192.168.x.x:3000) on your Android Chrome browser.Tap ⋮ → Add to Home screen.
Launch it from the new icon on your home screen.
You’ll now have:
✅ No Chrome address bar.
✅ No tabs or URL bar.
✅ A native-like fullscreen app.
🧠 Step 4 (optional) — Confirm manifest generation
Open your web-build/manifest.json and ensure it has entries like:
{
"display": "standalone",
"start_url": ".",
"background_color": "#ffffff",
"theme_color": "#000000",
"short_name": "YourAppName"
}
Once you confirm it works, you can later fine-tune icons, splash screen, or start URL for a fully polished PWA look.
Would you like me to show you the recommended extra fields to add (for icons, name, and splash colors) so it looks like a real installed app on Android?
------------
[Then I changed app.json as suggested by CG.]
@expo/webpack-config (version 19.0.1) expects expo to be at version ^49.0.7 or ^50.0.0-0. (Reddit)So when you’re using Expo SDK 52 (which is a newer version), you run into a peer-dependency conflict — npm notices the mismatch and refuses to install unless you force it or ignore peerDeps.
✅ What this means for you
If you install
@expo/webpack-config@19.0.1while using Expo SDK 52, you’ll get this sort of error because it lists:peer expo@"^49.0.7 || ^50.0.0-0" from @expo/webpack-config@19.0.1(Reddit)Also, the package’s own documentation says that "Expo Webpack is in maintenance mode" and they’ve been focusing on a new universal bundling solution. (npm)
🔍 Your options
Given this mismatch, you have a few paths:
Force install anyway
You can install@expo/webpack-configwith--legacy-peer-depsor--force, e.g.:npm install --save @expo/webpack-config --legacy-peer-depsThis may work, but you may run into subtle build issues or unsupported behaviour because you’re outside the officially declared peer-version range.
Use the Metro bundler / native Expo Router web output instead
Since you’re on SDK 52 (and you already used"bundler": "metro"for web in your config), it might be more stable to continue using the Metro web build or whatever Expo recommends for SDK 52 rather than forcing Webpack. The documentation indicates Expo is steering new projects away from Webpack. (Expo Documentation)Downgrade to an Expo version that aligns with the Webpack config peer-range
If you really need Webpack +@expo/webpack-configto work cleanly, you could use Expo SDK 50 (or whichever version the config supports) — but that might require upgrading/adjusting a lot of other libraries, so it’s more disruptive.Wait / upgrade to the newer bundling solution when Expo supports it
Since Expo indicates the Webpack config is in maintenance mode and they’re working on a new universal solution, you could keep using the current Metro setup, and when Expo releases the newer solution (or upgrades@expo/webpack-configfor newer Expo SDKs), you can migrate then.
🧾 Recommendation for your case
Since you are already on Expo SDK 52 and have a setup using Expo Router + Metro (for web bundling), I’d suggest going with Option 2 above:
Keep
"bundler": "metro"in your web config.Skip installing
@expo/webpack-configunless you really need the Webpack static export path.Use Expo’s recommended build path for web + PWA (or whichever is supported) rather than forcing the Webpack route.
If you do want to proceed with Webpack anyway (for PWA static export) and are comfortable accepting possible mismatches, you can install with --legacy-peer-deps, test thoroughly, and watch for issues.
If you like, I can check exactly what versions of @expo/webpack-config support Expo SDK 52 (or what workarounds people are using) and send you those version numbers & steps. Would you like me to dig into that?
---------
With Expo SDK 52 + Metro, “Add to Home screen” on mobile Chrome does not always guarantee true fullscreen unless a few conditions are met:
1️⃣ PWA manifest must actually exist and be served
Metro serves a runtime manifest for development, but Chrome sometimes ignores it if the URL is localhost, or if the manifest isn’t properly linked in
<head>.Check your dev server URL:
http://192.168.x.x:19006may not trigger Chrome’s PWA detection properly in dev mode.
✅ Workaround: test using a static build hosted on a local network (like npx serve web-build) rather than npx expo start --web.
2️⃣ Chrome’s “Add to Home screen” rules
Chrome only opens in standalone fullscreen if all of the following are true:
The site has a valid manifest.json with:
{ "display": "standalone", "start_url": ".", "name": "LMS", "short_name": "LMS", "background_color": "#ffffff", "theme_color": "#0d6c9a", "icons": [ ... ] }The manifest is linked in
<head>of the page:<link rel="manifest" href="/manifest.json" />The page is served over HTTPS (or localhost).
During development with Metro, the runtime manifest may not meet all these conditions, so Chrome just opens it in the normal browser tab.
3️⃣ ScrollView prevents Chrome auto-hiding
Even if the app opens as standalone, if your content is inside <ScrollView> with overflow: scroll, Chrome won’t detect “page scroll.”
That’s fine — with PWA standalone mode, Chrome removes the address bar anyway.
But if Metro doesn’t generate a proper manifest, Chrome never treats it as a standalone PWA.
✅ Recommended approach
Export a static PWA build (this ensures manifest.json is correctly created and linked):
Switch
"web": { "bundler": "webpack", "output": "static" }temporarily inapp.json.Install
@expo/webpack-config(use--legacy-peer-depsif needed).Run:
npx expo export:web npx serve web-buildOpen that local URL on Chrome mobile → Add to Home screen → Launch → should be fullscreen.
Keep Metro for dev, Webpack only for PWA export.
You don’t need to migrate your ScrollViews.
You get a real PWA experience, including fullscreen.
💡 Bottom line:
With Metro dev server, Chrome’s “Add to Home screen” cannot reliably open in fullscreen.
To test fullscreen PWA, you need a static Webpack export (or a hosted HTTPS site with manifest.json) — that’s the only reliable way for Chrome to open your app standalone.
Given your constraints and the desire to avoid Webpack, achieving true fullscreen behavior in mobile browsers may not be feasible with Expo SDK 52 and the Metro bundler alone. The Expo team has acknowledged limitations in this area, particularly with the handling of browser chrome on mobile devices. GitHub
If you are open to exploring alternative solutions, such as migrating to a more recent version of Expo that supports Webpack or considering other frameworks that offer better PWA support, I would be happy to assist you in evaluating those options.
Me: Webpack beta is the problem. Not webpack itself. I don't want to use any beta or canary stuff.
...
CG: Apologies for any confusion in my earlier responses. Upon reviewing the information, it appears that Expo SDK 53 does not provide stable support for @expo/webpack-config. The Expo team has deprecated @expo/webpack-config and recommends migrating to Expo Router for building web applications. (Expo Documentation)
Given this, achieving fullscreen behavior in mobile Chrome without using Webpack or Expo Router may not be feasible with Expo SDK 53. The recommended approach for implementing fullscreen behavior in mobile browsers is by configuring your app as a Progressive Web App (PWA) with a valid manifest file and serving it over HTTPS, which typically requires using Expo Router or a similar solution.
If you are open to exploring alternative solutions or need assistance with migrating to Expo Router, I would be happy to guide you through the process.
Me: My package.json deps already has:
"expo-router": "~4.0.20",
...
CG: Ah — right! In Expo SDK 52+ with Expo Router, the old expo build:web command is no longer supported in the local CLI.
The supported command is:
npx expo export:web
…but here’s the catch:
With your current Metro bundler setup,
expo export:webonly works with Webpack.Since you want to avoid any beta/canary
@expo/webpack-config, you cannot useexpo export:webreliably on SDK 52.That means: there is no stable way to generate a full static PWA for testing fullscreen without either:
Installing a compatible Webpack version (requires beta/canary for SDK 52), or
Upgrading Expo SDK to a version where stable Webpack export is supported (like SDK 53+). [Ravi: CG's message is confusing here as even with SDK 53+, stable Webpack export does not seem to be available. Only beta/canary Webpack export seems to be available. I am skipping the exchanges related to me pointing this out to CG.]
✅ Implication for your app
You can continue development with Metro + Expo Router (
npx expo start --web).You can Add to Home screen, but in dev mode on Chrome, address bar will still be visible.
To get real fullscreen PWA behavior, you will need either stable Webpack export (requires SDK upgrade) or host the app on a proper HTTPS server and let Chrome detect it as a PWA.
💡 Summary of your choices right now
| Option | Pros | Cons |
|---|---|---|
| Stay on Expo SDK 52 + Metro | Stable, no beta | Chrome shows address bar in dev, no reliable fullscreen |
| Force-install Webpack beta | Can run expo export:web and test fullscreen | Beta package, potential instability |
| Upgrade Expo SDK | Stable Webpack export possible [Ravi: This seems to be inaccurate. Please see related above comment of mine.] | Upgrade effort, may require dependency updates |
...
But that has old app.json without the additions you suggested.
...
Me: I would like to try out deploying my current app after confirming your app.json suggestions, to eas which serves it over https . Then I will load that in my mobile chrome browser and see if I get Full Screen via add to home. ... That would be relatively straightforward to try.
If that does not work, I will suspend this PWA effort. I do not want to get into manifest.json, service worker and all that stuff that Expo docs talk about for PWA.
What do u think of what I said?
Even with your updated
app.jsonand a deployed HTTPS Expo Hosting URL, Chrome still opens the app in a normal browser tab with the address bar.This means Metro + Expo Router alone (without Webpack / static PWA export) is not enough for Chrome to recognize your app as a standalone PWA.
In other words, the basic PWA fields in
app.jsonare not sufficient to trigger fullscreen on mobile Chrome in this setup.
Comments
Post a Comment